Compare commits

..

26 Commits

Author SHA1 Message Date
2dust
f488811f01 up 1.9.10 2024-11-04 16:49:52 +08:00
2dust
d212cda1e1 You can delete the downloaded geo file, which will be restored to the built-in geo file. 2024-11-04 16:47:49 +08:00
DecorativeFamily
ba760eac59 Update V2rayConfigManager.kt (#3847)
Update noise parameter

https://github.com/XTLS/Xray-docs-next/blob/main/docs/en/config/outbounds/freedom.md

"noises":[
{
"type":"base64",
"packet":"7nQBAAABAAAAAAAABnQtcmluZwZtc2VkZ2UDbmV0AAABAAE=",
"delay":"10-16"
},
{
"type":"rand",
"packet":"10-20",
"delay":"10-16"
},
{
"type":"str",
"packet":"hiGFW",
"delay":"10-16"
}
]

Add udp type":"base64",

@2dust
2024-11-04 09:42:45 +08:00
886963226
8549b5ea46 Optimization (#3842)
* Update PluginUtil.kt

fix kotlin.UninitializedProertyAcessException:lateinit property procService has not been initalized.

* Update ProcessService.kt
2024-11-04 09:42:17 +08:00
886963226
0b3c106c6f Update build.yml (#3841)
patch fix go
Do not use custom versions. Usually, major versions are bundled with the latest version.
Source:https://github.com/actions/toolkit/blob/main/docs/action-versioning.md
2024-11-04 09:32:16 +08:00
2dust
84e7ee4ef3 Bug fix 2024-11-03 20:06:43 +08:00
DecorativeFamily
e5d498ea6e Update V2rayConfigManager.kt (#3822)
* Update V2rayConfigManager.kt

Update some things

* Update V2rayConfigManager.kt

* Update V2rayConfigManager.kt
2024-11-03 19:29:48 +08:00
DecorativeFamily
6da835a2ca Update translation persian (#3835)
Update translation persian
2024-11-03 17:46:07 +08:00
solokot
1f9a71e6ac Update Russian translation (#3834) 2024-11-03 15:22:48 +08:00
2dust
9c92fdc257 Fix
https://github.com/2dust/v2rayNG/issues/3824
2024-11-02 14:36:45 +08:00
2dust
da219228fa Fix
https://github.com/2dust/v2rayNG/issues/3821
2024-11-01 20:57:11 +08:00
DecorativeFamily
c0a6455d08 Update Persian translation (#3818)
* Update Persian translation

Update Persian translation

* Update Persian translation

Update Persian translation

* Update Persian translation

Update Persian translation

* Update Persian translation

Update Persian translation

* Update Persian translation

Update Persian translation
2024-11-01 20:50:23 +08:00
2dust
709e2a9ed4 Improved settings storage 2024-11-01 20:43:17 +08:00
2dust
c3ac9f01d2 Refactor code 2024-11-01 19:01:24 +08:00
2dust
65eba3795f Bug fix
https://github.com/2dust/v2rayNG/issues/3807
2024-11-01 17:45:25 +08:00
2dust
341cdb5dbe Add migrate2ProfileCustom 2024-10-31 19:30:18 +08:00
2dust
4f43c2ce45 Reformat code 2024-10-31 19:30:04 +08:00
2dust
2ec691fc6b Add port hopping for hy2 2024-10-31 17:03:13 +08:00
2dust
81ed321654 Unit test 2024-10-31 14:16:39 +08:00
2dust
6ad37c70f1 Refactor server configuration storage 2024-10-31 14:10:09 +08:00
2dust
c7ffd6d82d Refactor V2rayConfig 2024-10-31 10:56:37 +08:00
any116
ae4b0fd8d3 Optimize up sub error log (#3805)
* Optimize up sub error log

Optimize the update subscription error log when not use proxy first.

* Update AngConfigManager.kt

* Update AngConfigManager.kt

* Update AngConfigManager.kt
2024-10-29 13:37:34 +08:00
DecorativeFamily
beceaba44d Update build.yml (#3804)
Fix restore cache failed
2024-10-29 13:36:00 +08:00
solokot
f5987d9767 Update Russian translation (#3802) 2024-10-29 13:35:35 +08:00
2dust
616712b338 Rename ProfileItem to ProfileLiteItem 2024-10-28 15:01:15 +08:00
2dust
076a968476 targetSdk = 35 2024-10-28 14:33:30 +08:00
86 changed files with 2148 additions and 1619 deletions

View File

@@ -21,12 +21,19 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version: '1.22.4'
go-version: '1.23.2'
cache: false
- name: Patch Go use 600296
#https://go-review.googlesource.com/c/go/+/600296
run: |
cd "$(go env GOROOT)"
curl "https://go-review.googlesource.com/changes/go~600296/revisions/5/patch" | base64 -d | patch --verbose -p 1
- name: Install gomobile
run: |

View File

@@ -10,9 +10,9 @@ android {
defaultConfig {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 603
versionName = "1.9.9"
targetSdk = 35
versionCode = 604
versionName = "1.9.10"
multiDexEnabled = true
splits {
abi {
@@ -91,6 +91,8 @@ android {
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
testImplementation(libs.junit)
testImplementation(libs.org.mockito.mockito.inline)
testImplementation(libs.mockito.kotlin)
implementation(libs.flexbox)
// Androidx

View File

@@ -224,8 +224,7 @@
<activity
android:name=".ui.TaskerActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
android:icon="@mipmap/ic_launcher">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter>
@@ -234,7 +233,8 @@
<receiver
android:name=".receiver.TaskerReceiver"
android:exported="true"
android:process=":RunSoLibV2RayDaemon">
android:process=":RunSoLibV2RayDaemon"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter>

View File

@@ -19,6 +19,7 @@ package com.v2ray.ang.helper;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
@@ -87,13 +88,13 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float maxSwipeDistance = viewHolder.itemView.getWidth() * SWIPE_THRESHOLD;
float swipeAmount = Math.abs(dX);
float direction = Math.signum(dX);
// 限制最大滑动距离
float translationX = Math.min(swipeAmount, maxSwipeDistance) * direction;
float alpha = ALPHA_FULL - Math.min(swipeAmount, maxSwipeDistance) / maxSwipeDistance;

View File

@@ -7,7 +7,7 @@ import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import androidx.work.WorkManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
class AngApplication : MultiDexApplication() {

View File

@@ -155,6 +155,7 @@ object AppConfig {
/** Give a good name to this, IDK*/
const val VPN = "VPN"
// Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
@@ -170,6 +171,12 @@ object AppConfig {
val DNS_ONE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
const val DEFAULT_NETWORK = "tcp"
const val TLS = "tls"
const val REALITY = "reality"
const val HEADER_TYPE_HTTP = "http"
}

View File

@@ -1,6 +1,6 @@
package com.v2ray.ang.dto
data class ConfigResult (
data class ConfigResult(
var status: Boolean,
var guid: String? = null,
var content: String = "",

View File

@@ -8,6 +8,7 @@ data class Hysteria2Bean(
val socks5: Socks5Bean? = null,
val http: Socks5Bean? = null,
val tls: TlsBean? = null,
val transport: TransportBean? = null,
) {
data class ObfsBean(
val type: String?,
@@ -25,5 +26,15 @@ data class Hysteria2Bean(
data class TlsBean(
val sni: String?,
val insecure: Boolean?,
val pinSHA256: String?,
)
data class TransportBean(
val type: String?,
val udp: TransportUdpBean?
) {
data class TransportUdpBean(
val hopInterval: String?,
)
}
}

View File

@@ -1,9 +1,75 @@
package com.v2ray.ang.dto
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.util.Utils
data class ProfileItem(
val configVersion: Int = 4,
val configType: EConfigType,
var subscriptionId: String = "",
var addedTime: Long = System.currentTimeMillis(),
var remarks: String = "",
var server: String?,
var serverPort: Int?,
)
var server: String? = null,
var serverPort: String? = null,
var password: String? = null,
var method: String? = null,
var flow: String? = null,
var username: String? = null,
var network: String? = null,
var headerType: String? = null,
var host: String? = null,
var path: String? = null,
var seed: String? = null,
var quicSecurity: String? = null,
var quicKey: String? = null,
var mode: String? = null,
var serviceName: String? = null,
var authority: String? = null,
var security: String? = null,
var sni: String? = null,
var alpn: String? = null,
var fingerPrint: String? = null,
var insecure: Boolean? = null,
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = null,
var secretKey: String? = null,
var localAddress: String? = null,
var reserved: String? = null,
var mtu: Int? = null,
var obfsPassword: String? = null,
var portHopping: String? = null,
var portHoppingInterval: String? = null,
var pinSHA256: String? = null,
) {
companion object {
fun create(configType: EConfigType): ProfileItem {
return ProfileItem(configType = configType)
}
}
fun getAllOutboundTags(): MutableList<String> {
return mutableListOf(TAG_PROXY, TAG_DIRECT, TAG_BLOCKED)
}
fun getServerAddressAndPort(): String {
return Utils.getIpv6Address(server) + ":" + serverPort
}
fun getKeyProperty(): ProfileItem {
val copy = this.copy()
copy.subscriptionId = ""
copy.addedTime = 0L
return copy
}
}

View File

@@ -0,0 +1,9 @@
package com.v2ray.ang.dto
data class ProfileLiteItem(
val configType: EConfigType,
var subscriptionId: String = "",
var remarks: String = "",
var server: String?,
var serverPort: Int?,
)

View File

@@ -7,6 +7,9 @@ import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.*
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.*
import com.v2ray.ang.util.Utils
import java.lang.reflect.Type
@@ -27,16 +30,6 @@ data class V2rayConfig(
var observatory: Any? = null,
var burstObservatory: Any? = null
) {
companion object {
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
const val DEFAULT_NETWORK = "tcp"
const val TLS = "tls"
const val REALITY = "reality"
const val HTTP = "http"
}
data class LogBean(
val access: String,
@@ -82,6 +75,49 @@ data class V2rayConfig(
val sendThrough: String? = null,
var mux: MuxBean? = MuxBean(false)
) {
companion object {
fun create(configType: EConfigType): OutboundBean? {
return when (configType) {
EConfigType.VMESS,
EConfigType.VLESS ->
return OutboundBean(
protocol = configType.name.lowercase(),
settings = OutSettingsBean(
vnext = listOf(
VnextBean(
users = listOf(UsersBean())
)
)
),
streamSettings = StreamSettingsBean()
)
EConfigType.SHADOWSOCKS,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.TROJAN,
EConfigType.HYSTERIA2 ->
return OutboundBean(
protocol = configType.name.lowercase(),
settings = OutSettingsBean(
servers = listOf(ServersBean())
),
streamSettings = StreamSettingsBean()
)
EConfigType.WIREGUARD ->
return OutboundBean(
protocol = configType.name.lowercase(),
settings = OutSettingsBean(
secretKey = "",
peers = listOf(WireGuardBean())
)
)
EConfigType.CUSTOM -> null
}
}
}
data class OutSettingsBean(
var vnext: List<VnextBean>? = null,
@@ -110,17 +146,17 @@ data class V2rayConfig(
data class VnextBean(
var address: String = "",
var port: Int = DEFAULT_PORT,
var port: Int = AppConfig.DEFAULT_PORT,
var users: List<UsersBean>
) {
data class UsersBean(
var id: String = "",
var alterId: Int? = null,
var security: String = DEFAULT_SECURITY,
var level: Int = DEFAULT_LEVEL,
var encryption: String = "",
var flow: String = ""
var security: String? = null,
var level: Int = AppConfig.DEFAULT_LEVEL,
var encryption: String? = null,
var flow: String? = null
)
}
@@ -141,8 +177,8 @@ data class V2rayConfig(
var method: String? = null,
var ota: Boolean = false,
var password: String? = null,
var port: Int = DEFAULT_PORT,
var level: Int = DEFAULT_LEVEL,
var port: Int = AppConfig.DEFAULT_PORT,
var level: Int = AppConfig.DEFAULT_LEVEL,
val email: String? = null,
var flow: String? = null,
val ivCheck: Boolean? = null,
@@ -151,7 +187,7 @@ data class V2rayConfig(
data class SocksUsersBean(
var user: String = "",
var pass: String = "",
var level: Int = DEFAULT_LEVEL
var level: Int = AppConfig.DEFAULT_LEVEL
)
}
@@ -164,7 +200,7 @@ data class V2rayConfig(
}
data class StreamSettingsBean(
var network: String = DEFAULT_NETWORK,
var network: String = AppConfig.DEFAULT_NETWORK,
var security: String = "",
var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean? = null,
@@ -224,7 +260,7 @@ data class V2rayConfig(
}
data class WsSettingsBean(
var path: String = "",
var path: String? = null,
var headers: HeadersBean = HeadersBean(),
val maxEarlyData: Int? = null,
val useBrowserForwarding: Boolean? = null,
@@ -234,21 +270,21 @@ data class V2rayConfig(
}
data class HttpupgradeSettingsBean(
var path: String = "",
var host: String = "",
var path: String? = null,
var host: String? = null,
val acceptProxyProtocol: Boolean? = null
)
data class SplithttpSettingsBean(
var path: String = "",
var host: String = "",
var path: String? = null,
var host: String? = null,
val maxUploadSize: Int? = null,
val maxConcurrentUploads: Int? = null
)
data class HttpSettingsBean(
var host: List<String> = ArrayList(),
var path: String = ""
var path: String? = null
)
data class SockoptBean(
@@ -262,7 +298,7 @@ data class V2rayConfig(
data class TlsSettingsBean(
var allowInsecure: Boolean = false,
var serverName: String = "",
var serverName: String? = null,
val alpn: List<String>? = null,
val minVersion: String? = null,
val maxVersion: String? = null,
@@ -311,14 +347,14 @@ data class V2rayConfig(
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
authority: String?
): String {
var sni = ""
): String? {
var sni: String? = null
network = transport
when (network) {
"tcp" -> {
val tcpSetting = TcpSettingsBean()
if (headerType == HTTP) {
tcpSetting.header.type = HTTP
if (headerType == AppConfig.HEADER_TYPE_HTTP) {
tcpSetting.header.type = AppConfig.HEADER_TYPE_HTTP
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
requestObj.headers.Host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
@@ -400,7 +436,7 @@ data class V2rayConfig(
}
fun populateTlsSettings(
streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
streamSecurity: String, allowInsecure: Boolean, sni: String?, fingerprint: String?, alpns: String?,
publicKey: String?, shortId: String?, spiderX: String?
) {
security = streamSecurity
@@ -413,10 +449,10 @@ data class V2rayConfig(
shortId = shortId,
spiderX = spiderX
)
if (security == TLS) {
if (security == AppConfig.TLS) {
tlsSettings = tlsSetting
realitySettings = null
} else if (security == REALITY) {
} else if (security == AppConfig.REALITY) {
tlsSettings = null
realitySettings = tlsSetting
}
@@ -501,7 +537,7 @@ data class V2rayConfig(
}
}
fun getTransportSettingDetails(): List<String>? {
fun getTransportSettingDetails(): List<String?>? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
@@ -601,7 +637,8 @@ data class V2rayConfig(
var port: Int? = null,
var domains: List<String>? = null,
var expectIPs: List<String>? = null,
val clientIp: String? = null
val clientIp: String? = null,
val skipFallback: Boolean? = null,
)
}

View File

@@ -93,4 +93,6 @@ inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = whe
inline fun <reified T : Serializable> Intent.serializable(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
}
}
inline fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())

View File

@@ -0,0 +1,21 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.JsonUtil
object CustomFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.CUSTOM)
val fullConfig = JsonUtil.fromJson(str, V2rayConfig::class.java)
val outbound = fullConfig.getProxyOutbound()
config.remarks = fullConfig?.remarks ?: System.currentTimeMillis().toString()
config.server = outbound?.getServerAddress()
config.serverPort = outbound?.getServerPort().toString()
return config
}
}

View File

@@ -0,0 +1,84 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.util.Utils
import java.net.URI
open class FmtBase {
fun toUri(config: ProfileItem, userInfo: String?, dicQuery: HashMap<String, String>?): String {
val query = if (dicQuery != null)
("?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) }))
else ""
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(config.server),
config.serverPort
)
return "${url}${query}#${Utils.urlEncode(config.remarks)}"
}
fun getQueryParam(uri: URI): Map<String, String> {
return uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
}
fun getQueryDic(config: ProfileItem): HashMap<String, String> {
val dicQuery = HashMap<String, String>()
dicQuery["security"] = config.security?.ifEmpty { "none" }.orEmpty()
config.sni.let { if (it.isNotNullEmpty()) dicQuery["sni"] = it.orEmpty() }
config.alpn.let { if (it.isNotNullEmpty()) dicQuery["alpn"] = it.orEmpty() }
config.fingerPrint.let { if (it.isNotNullEmpty()) dicQuery["fp"] = it.orEmpty() }
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.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
dicQuery["type"] = config.network?.ifEmpty { AppConfig.DEFAULT_NETWORK }.orEmpty()
when (config.network) {
"tcp" -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
}
"kcp" -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
}
"ws", "httpupgrade", "splithttp" -> {
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
}
"http", "h2" -> {
dicQuery["type"] = "http"
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
}
"quic" -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.quicSecurity.let { if (it.isNotNullEmpty()) dicQuery["quicSecurity"] = it.orEmpty() }
config.quicKey.let { if (it.isNotNullEmpty()) dicQuery["key"] = it.orEmpty() }
}
"grpc" -> {
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
config.authority.let { if (it.isNotNullEmpty()) dicQuery["authority"] = it.orEmpty() }
config.serviceName.let { if (it.isNotNullEmpty()) dicQuery["serviceName"] = it.orEmpty() }
}
}
return dicQuery
}
}

View File

@@ -0,0 +1,28 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.isNotNullEmpty
import kotlin.text.orEmpty
object HttpFmt : FmtBase() {
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.HTTP)
outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = profileItem.username.orEmpty()
socksUsersBean.pass = profileItem.password.orEmpty()
server.users = listOf(socksUsersBean)
}
}
return outboundBean
}
}

View File

@@ -0,0 +1,120 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.Hysteria2Bean
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object Hysteria2Fmt : FmtBase() {
fun parse(str: String): ProfileItem? {
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.HYSTERIA2)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
config.security = AppConfig.TLS
if (!uri.rawQuery.isNullOrEmpty()) {
val queryParam = getQueryParam(uri)
config.security = queryParam["security"] ?: AppConfig.TLS
config.insecure = if (queryParam["insecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["insecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.alpn = queryParam["alpn"]
config.obfsPassword = queryParam["obfs-password"]
config.portHopping = queryParam["mport"]
config.pinSHA256 = queryParam["pinSHA256"]
}
return config
}
fun toUri(config: ProfileItem): String {
val dicQuery = HashMap<String, String>()
config.security.let { if (it != null) dicQuery["security"] = it }
config.sni.let { if (it.isNotNullEmpty()) dicQuery["sni"] = it.orEmpty() }
config.alpn.let { if (it.isNotNullEmpty()) dicQuery["alpn"] = it.orEmpty() }
config.insecure.let { dicQuery["insecure"] = if (it == true) "1" else "0" }
if (config.obfsPassword.isNotNullEmpty()) {
dicQuery["obfs"] = "salamander"
dicQuery["obfs-password"] = config.obfsPassword.orEmpty()
}
if (config.portHopping.isNotNullEmpty()) {
dicQuery["mport"] = config.portHopping.orEmpty()
}
if (config.pinSHA256.isNotNullEmpty()) {
dicQuery["pinSHA256"] = config.pinSHA256.orEmpty()
}
return toUri(config, config.password, dicQuery)
}
fun toNativeConfig(config: ProfileItem, socksPort: Int): Hysteria2Bean? {
val obfs = if (config.obfsPassword.isNullOrEmpty()) null else
Hysteria2Bean.ObfsBean(
type = "salamander",
salamander = Hysteria2Bean.ObfsBean.SalamanderBean(
password = config.obfsPassword
)
)
val transport = if (config.portHopping.isNullOrEmpty()) null else
Hysteria2Bean.TransportBean(
type = "udp",
udp = Hysteria2Bean.TransportBean.TransportUdpBean(
hopInterval = (config.portHoppingInterval ?: "30") + "s"
)
)
val server =
if (config.portHopping.isNullOrEmpty())
config.getServerAddressAndPort()
else
Utils.getIpv6Address(config.server) + ":" + config.portHopping
val bean = Hysteria2Bean(
server = server,
auth = config.password,
obfs = obfs,
transport = transport,
socks5 = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
http = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
tls = Hysteria2Bean.TlsBean(
sni = config.sni ?: config.server,
insecure = config.insecure,
pinSHA256 = if (config.pinSHA256.isNullOrEmpty()) null else config.pinSHA256
)
)
return bean
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.HYSTERIA2)
return outboundBean
}
}

View File

@@ -0,0 +1,65 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.SHADOWSOCKS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.idnHost.isEmpty() || uri.userInfo.isEmpty()) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
val result = if (uri.userInfo.contains(":")) {
uri.userInfo.split(":")
} else {
Utils.decode(uri.userInfo).split(":")
}
if (result.count() == 2) {
config.method = result.first()
config.password = result.last()
}
if (!uri.rawQuery.isNullOrEmpty()) {
val queryParam = getQueryParam(uri)
if (queryParam["plugin"] == "obfs-local" && queryParam["obfs"] == "http") {
config.network = "tcp"
config.headerType = "http"
config.host = queryParam["obfs-host"]
config.path = queryParam["path"]
}
}
return config
}
fun toUri(config: ProfileItem): String {
val pw = "${config.method}:${config.password}"
return toUri(config, pw, null)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.method = profileItem.method
}
return outboundBean
}
}

View File

@@ -0,0 +1,61 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.util.Utils
import java.net.URI
import kotlin.text.orEmpty
object SocksFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.SOCKS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.idnHost.isEmpty()) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
if (uri.userInfo?.isEmpty() == false) {
val result = Utils.decode(uri.userInfo).split(":")
if (result.count() == 2) {
config.username = result.first()
config.password = result.last()
}
}
return config
}
fun toUri(config: ProfileItem): String {
val pw =
if (config.username.isNotNullEmpty())
"${config.username}:${config.password}"
else
":"
return toUri(config, pw, null)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.SOCKS)
outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = profileItem.username.orEmpty()
socksUsersBean.pass = profileItem.password.orEmpty()
server.users = listOf(socksUsersBean)
}
}
return outboundBean
}
}

View File

@@ -0,0 +1,103 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
import kotlin.text.orEmpty
object TrojanFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.TROJAN)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
if (uri.rawQuery.isNullOrEmpty()) {
config.security = AppConfig.TLS
config.insecure = allowInsecure
} else {
val queryParam = getQueryParam(uri)
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.flow = queryParam["flow"]
}
return config
}
fun toUri(config: ProfileItem): String {
val dicQuery = getQueryDic(config)
return toUri(config, config.password, dicQuery)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.TROJAN)
outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.flow = profileItem.flow
}
outboundBean?.streamSettings?.populateTransportSettings(
profileItem.network.orEmpty(),
profileItem.headerType,
profileItem.host,
profileItem.path,
profileItem.seed,
profileItem.quicSecurity,
profileItem.quicKey,
profileItem.mode,
profileItem.serviceName,
profileItem.authority,
)
outboundBean?.streamSettings?.populateTlsSettings(
profileItem.security.orEmpty(),
profileItem.insecure == true,
profileItem.sni,
profileItem.fingerPrint,
profileItem.alpn,
profileItem.publicKey,
profileItem.shortId,
profileItem.spiderX,
)
return outboundBean
}
}

View File

@@ -0,0 +1,104 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object VlessFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.VLESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
config.method = queryParam["encryption"] ?: "none"
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.flow = queryParam["flow"]
return config
}
fun toUri(config: ProfileItem): String {
val dicQuery = getQueryDic(config)
dicQuery["encryption"] = config.method ?: "none"
return toUri(config, config.password, dicQuery)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.VLESS)
outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].encryption = profileItem.method
vnext.users[0].flow = profileItem.flow
}
outboundBean?.streamSettings?.populateTransportSettings(
profileItem.network.orEmpty(),
profileItem.headerType,
profileItem.host,
profileItem.path,
profileItem.seed,
profileItem.quicSecurity,
profileItem.quicKey,
profileItem.mode,
profileItem.serviceName,
profileItem.authority,
)
outboundBean?.streamSettings?.populateTlsSettings(
profileItem.security.orEmpty(),
profileItem.insecure == true,
profileItem.sni,
profileItem.fingerPrint,
profileItem.alpn,
profileItem.publicKey,
profileItem.shortId,
profileItem.spiderX,
)
return outboundBean
}
}

View File

@@ -0,0 +1,194 @@
package com.v2ray.ang.fmt
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import java.net.URI
import kotlin.text.orEmpty
object VmessFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
return parseVmessStd(str)
}
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.VMESS)
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
return null
}
val vmessQRCode = JsonUtil.fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
return null
}
config.remarks = vmessQRCode.ps
config.server = vmessQRCode.add
config.serverPort = vmessQRCode.port
config.password = vmessQRCode.id
config.method = if (TextUtils.isEmpty(vmessQRCode.scy)) AppConfig.DEFAULT_SECURITY else vmessQRCode.scy
config.network = vmessQRCode.net ?: "tcp"
config.headerType = vmessQRCode.type
config.host = vmessQRCode.host
config.path = vmessQRCode.path
when (config.network) {
"kcp" -> {
config.seed = vmessQRCode.path
}
"quic" -> {
config.quicSecurity = vmessQRCode.host
config.quicKey = vmessQRCode.path
}
"grpc" -> {
config.mode = vmessQRCode.type
config.serviceName = vmessQRCode.path
config.authority = vmessQRCode.host
}
}
config.security = vmessQRCode.tls
config.insecure = allowInsecure
config.sni = vmessQRCode.sni
config.fingerPrint = vmessQRCode.fp
config.alpn = vmessQRCode.alpn
return config
}
fun toUri(config: ProfileItem): String {
val vmessQRCode = VmessQRCode()
vmessQRCode.v = "2"
vmessQRCode.ps = config.remarks
vmessQRCode.add = config.server.orEmpty()
vmessQRCode.port = config.serverPort.orEmpty()
vmessQRCode.id = config.password.orEmpty()
vmessQRCode.scy = config.method.orEmpty()
vmessQRCode.aid = "0"
vmessQRCode.net = config.network.orEmpty()
vmessQRCode.type = config.headerType.orEmpty()
when (config.network) {
"kcp" -> {
vmessQRCode.path = config.seed.orEmpty()
}
"quic" -> {
vmessQRCode.host = config.quicSecurity.orEmpty()
vmessQRCode.path = config.quicKey.orEmpty()
}
"grpc" -> {
vmessQRCode.type = config.mode.orEmpty()
vmessQRCode.path = config.serviceName.orEmpty()
vmessQRCode.host = config.authority.orEmpty()
}
}
config.host.let { if (it.isNotNullEmpty()) vmessQRCode.host = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) vmessQRCode.path = it.orEmpty() }
vmessQRCode.tls = config.security.orEmpty()
vmessQRCode.sni = config.sni.orEmpty()
vmessQRCode.fp = config.fingerPrint.orEmpty()
vmessQRCode.alpn = config.alpn.orEmpty()
val json = JsonUtil.toJson(vmessQRCode)
return Utils.encode(json)
}
fun parseVmessStd(str: String): ProfileItem? {
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.VMESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
config.method = AppConfig.DEFAULT_SECURITY
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
return config
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.VMESS)
outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].security = profileItem.method
}
outboundBean?.streamSettings?.populateTransportSettings(
profileItem.network.orEmpty(),
profileItem.headerType,
profileItem.host,
profileItem.path,
profileItem.seed,
profileItem.quicSecurity,
profileItem.quicKey,
profileItem.mode,
profileItem.serviceName,
profileItem.authority,
)
outboundBean?.streamSettings?.populateTlsSettings(
profileItem.security.orEmpty(),
profileItem.insecure == true,
profileItem.sni,
profileItem.fingerPrint,
profileItem.alpn,
null,
null,
null
)
return outboundBean
}
}

View File

@@ -0,0 +1,95 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
import kotlin.text.orEmpty
object WireguardFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.WIREGUARD)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.secretKey = uri.userInfo
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
config.publicKey = queryParam["publickey"].orEmpty()
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
config.reserved = (queryParam["reserved"] ?: "0,0,0")
return config
}
fun parseWireguardConfFile(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.WIREGUARD)
val queryParam: MutableMap<String, String> = mutableMapOf()
var currentSection: String? = null
str.lines().forEach { line ->
val trimmedLine = line.trim()
when {
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
trimmedLine.isBlank() || trimmedLine.startsWith("#") -> Unit // Skip blank lines or comments
currentSection != null -> {
val (key, value) = trimmedLine.split("=").map { it.trim() }
queryParam[key.lowercase()] = value // Store the key in lowercase for case-insensitivity
}
}
}
config.secretKey = queryParam["privatekey"].orEmpty()
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
config.publicKey = queryParam["publickey"].orEmpty()
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
config.reserved = (queryParam["reserved"] ?: "0,0,0")
return config
}
fun toUri(config: ProfileItem): String {
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] = config.publicKey.orEmpty()
if (config.reserved != null) {
dicQuery["reserved"] = Utils.removeWhiteSpace(config.reserved).orEmpty()
}
dicQuery["address"] = Utils.removeWhiteSpace(config.localAddress).orEmpty()
if (config.mtu != null) {
dicQuery["mtu"] = config.mtu.toString()
}
return toUri(config, config.secretKey, dicQuery)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.WIREGUARD)
outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = profileItem.secretKey
wireguard.address = (profileItem.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4).split(",")
wireguard.peers?.get(0)?.publicKey = profileItem.publicKey.orEmpty()
wireguard.peers?.get(0)?.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
wireguard.mtu = profileItem.mtu?.toInt()
wireguard.reserved = profileItem.reserved?.split(",")?.map { it.toInt() }
}
return outboundBean
}
}

View File

@@ -1,29 +1,25 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import android.content.Context
import android.graphics.Bitmap
import android.text.TextUtils
import android.util.Log
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.HY2
import com.v2ray.ang.R
import com.v2ray.ang.dto.*
import com.v2ray.ang.util.fmt.Hysteria2Fmt
import com.v2ray.ang.util.fmt.ShadowsocksFmt
import com.v2ray.ang.util.fmt.SocksFmt
import com.v2ray.ang.util.fmt.TrojanFmt
import com.v2ray.ang.util.fmt.VlessFmt
import com.v2ray.ang.util.fmt.VmessFmt
import com.v2ray.ang.util.fmt.WireguardFmt
import java.lang.reflect.Type
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt
import com.v2ray.ang.fmt.TrojanFmt
import com.v2ray.ang.fmt.VlessFmt
import com.v2ray.ang.fmt.VmessFmt
import com.v2ray.ang.fmt.WireguardFmt
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.Utils
import java.net.URI
import java.util.*
object AngConfigManager {
/**
@@ -33,7 +29,7 @@ object AngConfigManager {
str: String?,
subid: String,
subItem: SubscriptionItem?,
removedSelectedServer: ServerConfig?
removedSelectedServer: ProfileItem?
): Int {
try {
if (str == null || TextUtils.isEmpty(str)) {
@@ -71,12 +67,7 @@ object AngConfigManager {
config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null &&
config.getProxyOutbound()
?.getServerAddress() == removedSelectedServer.getProxyOutbound()
?.getServerAddress() &&
config.getProxyOutbound()
?.getServerPort() == removedSelectedServer.getProxyOutbound()
?.getServerPort()
config.server == removedSelectedServer.server && config.serverPort == removedSelectedServer.serverPort
) {
MmkvManager.setSelectServer(guid)
}
@@ -177,7 +168,7 @@ object AngConfigManager {
fun shareFullContent2Clipboard(context: Context, guid: String?): Int {
try {
if (guid == null) return -1
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
val result = V2rayConfigManager.getV2rayConfig(context, guid)
if (result.status) {
Utils.setClipboard(context, result.content)
} else {
@@ -218,6 +209,7 @@ object AngConfigManager {
var count = 0
servers.lines()
.distinct()
.forEach { str ->
if (Utils.isValidSubUrl(str)) {
count += importUrlAsSubscription(str)
@@ -255,6 +247,7 @@ object AngConfigManager {
val subItem = MmkvManager.decodeSubscription(subid)
var count = 0
servers.lines()
.distinct()
.reversed()
.forEach {
val resId = parseConfig(it, subid, subItem, removedSelectedServer)
@@ -284,12 +277,7 @@ object AngConfigManager {
if (serverList.isNotEmpty()) {
var count = 0
for (srv in serverList.reversed()) {
val config = ServerConfig.create(EConfigType.CUSTOM)
config.fullConfig =
JsonUtil.fromJson(JsonUtil.toJson(srv), V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
.toString())
val config = CustomFmt.parse(server) ?: continue
config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
@@ -303,10 +291,8 @@ object AngConfigManager {
try {
// For compatibility
val config = ServerConfig.create(EConfigType.CUSTOM)
val config = CustomFmt.parse(server) ?: return 0
config.subscriptionId = subid
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, server)
return 1
@@ -316,9 +302,7 @@ object AngConfigManager {
return 0
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
try {
val config = WireguardFmt.parseWireguardConfFile(server)
?: return R.string.toast_incorrect_protocol
config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val config = WireguardFmt.parseWireguardConfFile(server) ?: return R.string.toast_incorrect_protocol
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, server)
return 1
@@ -365,7 +349,8 @@ object AngConfigManager {
val httpPort = SettingsManager.getHttpPort()
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
} catch (e: Exception) {
e.printStackTrace()
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error, try……")
//e.printStackTrace()
""
}
if (configText.isEmpty()) {

View File

@@ -0,0 +1,190 @@
package com.v2ray.ang.handler
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
object MigrateManager {
private const val ID_SERVER_CONFIG = "SERVER_CONFIG"
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
fun migrateServerConfig2Profile(): Boolean {
if (serverStorage.count().toInt() == 0) {
return false
}
var serverList = serverStorage.allKeys() ?: return false
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + serverList.count())
for (guid in serverList) {
var configOld = decodeServerConfigOld(guid) ?: continue
var config = decodeServerConfig(guid)
if (config != null) {
serverStorage.remove(guid)
continue
}
config = migrateServerConfig2ProfileSub(configOld) ?: continue
config.subscriptionId = configOld.subscriptionId
MmkvManager.encodeServerConfig(guid, config)
//check and remove old
decodeServerConfig(guid) ?: continue
serverStorage.remove(guid)
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + config.remarks)
}
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-end")
return true
}
private fun migrateServerConfig2ProfileSub(configOld: ServerConfig): ProfileItem? {
return when (configOld.getProxyOutbound()?.protocol) {
EConfigType.VMESS.name.lowercase() -> migrate2ProfileCommon(configOld)
EConfigType.VLESS.name.lowercase() -> migrate2ProfileCommon(configOld)
EConfigType.TROJAN.name.lowercase() -> migrate2ProfileCommon(configOld)
EConfigType.SHADOWSOCKS.name.lowercase() -> migrate2ProfileCommon(configOld)
EConfigType.SOCKS.name.lowercase() -> migrate2ProfileSocks(configOld)
EConfigType.HTTP.name.lowercase() -> migrate2ProfileHttp(configOld)
EConfigType.WIREGUARD.name.lowercase() -> migrate2ProfileWireguard(configOld)
EConfigType.HYSTERIA2.name.lowercase() -> migrate2ProfileHysteria2(configOld)
EConfigType.CUSTOM.name.lowercase() -> migrate2ProfileCustom(configOld)
else -> null
}
}
private fun migrate2ProfileCommon(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(configOld.configType)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.method = outbound.getSecurityEncryption()
config.password = outbound.getPassword()
config.flow = outbound?.settings?.vnext?.get(0)?.users[0]?.flow ?: outbound?.settings?.servers?.get(0)?.flow
outbound.getTransportSettingDetails()?.let { transportDetails ->
config.network = "tcp"
config.headerType = transportDetails[0].orEmpty()
config.host = transportDetails[1].orEmpty()
config.path = transportDetails[2].orEmpty()
}
config.seed = outbound?.streamSettings?.kcpSettings?.seed
config.quicSecurity = outbound?.streamSettings?.quicSettings?.security
config.quicKey = outbound?.streamSettings?.quicSettings?.key
config.mode = if (outbound?.streamSettings?.grpcSettings?.multiMode == true) "multi" else "gun"
config.serviceName = outbound?.streamSettings?.grpcSettings?.serviceName
config.authority = outbound?.streamSettings?.grpcSettings?.authority
config.security = outbound.streamSettings?.security
val tlsSettings = outbound?.streamSettings?.realitySettings ?: outbound?.streamSettings?.tlsSettings
config.insecure = tlsSettings?.allowInsecure
config.sni = tlsSettings?.serverName
config.fingerPrint = tlsSettings?.fingerprint
config.alpn = Utils.removeWhiteSpace(tlsSettings?.alpn?.joinToString(",")).toString()
config.publicKey = tlsSettings?.publicKey
config.shortId = tlsSettings?.shortId
config.spiderX = tlsSettings?.spiderX
return config
}
private fun migrate2ProfileSocks(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(EConfigType.SOCKS)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
config.password = outbound.getPassword()
return config
}
private fun migrate2ProfileHttp(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(EConfigType.HTTP)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
config.password = outbound.getPassword()
return config
}
private fun migrate2ProfileWireguard(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(EConfigType.WIREGUARD)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
outbound.settings?.let { wireguard ->
config.secretKey = wireguard.secretKey
config.localAddress = Utils.removeWhiteSpace((wireguard.address as List<*>).joinToString(",")).toString()
config.publicKey = wireguard.peers?.getOrNull(0)?.publicKey
config.mtu = wireguard.mtu
config.reserved = Utils.removeWhiteSpace(wireguard.reserved?.joinToString(",")).toString()
}
return config
}
private fun migrate2ProfileHysteria2(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(EConfigType.HYSTERIA2)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.password = outbound.getPassword()
config.security = AppConfig.TLS
outbound.streamSettings?.tlsSettings?.let { tlsSetting ->
config.insecure = tlsSetting.allowInsecure
config.sni = tlsSetting.serverName
config.alpn = Utils.removeWhiteSpace(tlsSetting.alpn?.joinToString(",")).orEmpty()
}
config.obfsPassword = outbound.settings?.obfsPassword
return config
}
private fun migrate2ProfileCustom(configOld: ServerConfig): ProfileItem? {
val config = ProfileItem.create(EConfigType.CUSTOM)
val outbound = configOld.getProxyOutbound() ?: return null
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
return config
}
private fun decodeServerConfigOld(guid: String): ServerConfig? {
if (guid.isBlank()) {
return null
}
val json = serverStorage.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return JsonUtil.fromJson(json, ServerConfig::class.java)
}
}

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import com.tencent.mmkv.MMKV
@@ -8,16 +8,17 @@ 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.ServerConfig
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
object MmkvManager {
//region private
//private const val ID_PROFILE_CONFIG = "PROFILE_CONFIG"
private const val ID_MAIN = "MAIN"
private const val ID_SERVER_CONFIG = "SERVER_CONFIG"
private const val ID_PROFILE_CONFIG = "PROFILE_CONFIG"
private const val ID_PROFILE_FULL_CONFIG = "PROFILE_FULL_CONFIG"
private const val ID_SERVER_RAW = "SERVER_RAW"
private const val ID_SERVER_AFF = "SERVER_AFF"
private const val ID_SUB = "SUB"
@@ -27,14 +28,14 @@ object MmkvManager {
private const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
private const val KEY_SUB_IDS = "SUB_IDS"
//private val profileStorage by lazy { MMKV.mmkvWithID(ID_PROFILE_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
val settingsStorage by lazy { MMKV.mmkvWithID(ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val profileStorage by lazy { MMKV.mmkvWithID(ID_PROFILE_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val profileFullStorage by lazy { MMKV.mmkvWithID(ID_PROFILE_FULL_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
//endregion
@@ -61,31 +62,32 @@ object MmkvManager {
}
}
fun decodeServerConfig(guid: String): ServerConfig? {
if (guid.isBlank()) {
return null
}
val json = serverStorage.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return JsonUtil.fromJson(json, ServerConfig::class.java)
}
fun decodeProfileConfig(guid: String): ProfileItem? {
fun decodeServerConfig(guid: String): ProfileItem? {
if (guid.isBlank()) {
return null
}
val json = profileStorage.decodeString(guid)
val json = profileFullStorage.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return JsonUtil.fromJson(json, ProfileItem::class.java)
}
fun encodeServerConfig(guid: String, config: ServerConfig): String {
// fun decodeProfileConfig(guid: String): ProfileLiteItem? {
// if (guid.isBlank()) {
// return null
// }
// val json = profileStorage.decodeString(guid)
// if (json.isNullOrBlank()) {
// return null
// }
// return JsonUtil.fromJson(json, ProfileLiteItem::class.java)
// }
fun encodeServerConfig(guid: String, config: ProfileItem): String {
val key = guid.ifBlank { Utils.getUuid() }
serverStorage.encode(key, JsonUtil.toJson(config))
profileFullStorage.encode(key, JsonUtil.toJson(config))
val serverList = decodeServerList()
if (!serverList.contains(key)) {
serverList.add(0, key)
@@ -94,14 +96,14 @@ object MmkvManager {
mainStorage.encode(KEY_SELECTED_SERVER, key)
}
}
val profile = ProfileItem(
configType = config.configType,
subscriptionId = config.subscriptionId,
remarks = config.remarks,
server = config.getProxyOutbound()?.getServerAddress(),
serverPort = config.getProxyOutbound()?.getServerPort(),
)
profileStorage.encode(key, JsonUtil.toJson(profile))
// val profile = ProfileLiteItem(
// configType = config.configType,
// subscriptionId = config.subscriptionId,
// remarks = config.remarks,
// server = config.getProxyOutbound()?.getServerAddress(),
// serverPort = config.getProxyOutbound()?.getServerPort(),
// )
// profileStorage.encode(key, JsonUtil.toJson(profile))
return key
}
@@ -115,8 +117,8 @@ object MmkvManager {
val serverList = decodeServerList()
serverList.remove(guid)
encodeServerList(serverList)
serverStorage.remove(guid)
profileStorage.remove(guid)
profileFullStorage.remove(guid)
//profileStorage.remove(guid)
serverAffStorage.remove(guid)
}
@@ -124,7 +126,7 @@ object MmkvManager {
if (subid.isBlank()) {
return
}
serverStorage.allKeys()?.forEach { key ->
profileFullStorage.allKeys()?.forEach { key ->
decodeServerConfig(key)?.let { config ->
if (config.subscriptionId == subid) {
removeServer(key)
@@ -164,8 +166,8 @@ object MmkvManager {
fun removeAllServer() {
mainStorage.clearAll()
serverStorage.clearAll()
profileStorage.clearAll()
profileFullStorage.clearAll()
//profileStorage.clearAll()
serverAffStorage.clearAll()
}
@@ -192,7 +194,7 @@ object MmkvManager {
}
fun decodeServerRaw(guid: String): String? {
return serverRawStorage.decodeString(guid) ?: return null
return serverRawStorage.decodeString(guid)
}
//endregion
@@ -302,9 +304,51 @@ object MmkvManager {
fun encodeRoutingRulesets(rulesetList: MutableList<RulesetItem>?) {
if (rulesetList.isNullOrEmpty())
settingsStorage.encode(PREF_ROUTING_RULESET, "")
encodeSettings(PREF_ROUTING_RULESET, "")
else
settingsStorage.encode(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList))
encodeSettings(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList))
}
//endregion
fun encodeSettings(key: String, value: String?): Boolean {
return settingsStorage.encode(key, value)
}
fun encodeSettings(key: String, value: Int): Boolean {
return settingsStorage.encode(key, value)
}
fun encodeSettings(key: String, value: Boolean): Boolean {
return settingsStorage.encode(key, value)
}
fun encodeSettings(key: String, value: MutableSet<String>): Boolean {
return settingsStorage.encode(key, value)
}
fun decodeSettingsString(key: String): String? {
return settingsStorage.decodeString(key)
}
fun decodeSettingsString(key: String, defaultValue: String?): String? {
return settingsStorage.decodeString(key, defaultValue)
}
fun decodeSettingsBool(key: String): Boolean {
return settingsStorage.decodeBool(key)
}
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {
return settingsStorage.decodeBool(key, defaultValue)
}
fun decodeSettingsInt(key: String, defaultValue: Int): Int {
return settingsStorage.decodeInt(key, defaultValue)
}
fun decodeSettingsStringSet(key: String): MutableSet<String>? {
return settingsStorage.decodeStringSet(key)
}
//endregion
@@ -312,11 +356,11 @@ object MmkvManager {
//region Others
fun encodeStartOnBoot(startOnBoot: Boolean) {
settingsStorage.encode(PREF_IS_BOOTED, startOnBoot)
MmkvManager.encodeSettings(PREF_IS_BOOTED, startOnBoot)
}
fun decodeStartOnBoot(): Boolean {
return settingsStorage.decodeBool(PREF_IS_BOOTED, false)
return decodeSettingsBool(PREF_IS_BOOTED, false)
}
//endregion

View File

@@ -1,21 +1,26 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import android.content.Context
import android.content.res.AssetManager
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
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.dto.ProfileItem
import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
import com.v2ray.ang.util.MmkvManager.decodeServerConfig
import com.v2ray.ang.util.MmkvManager.decodeServerList
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerList
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.Utils.parseInt
import java.io.File
import java.io.FileOutputStream
import java.util.Collections
import kotlin.Int
object SettingsManager {
@@ -131,26 +136,51 @@ object SettingsManager {
MmkvManager.encodeSubsList(subsList)
}
fun getServerViaRemarks(remarks: String?): ServerConfig? {
fun getServerViaRemarks(remarks: String?): ProfileItem? {
if (remarks == null) {
return null
}
val serverList = decodeServerList()
for (guid in serverList) {
val profile = decodeProfileConfig(guid)
val profile = decodeServerConfig(guid)
if (profile != null && profile.remarks == remarks) {
return decodeServerConfig(guid)
return profile
}
}
return null
}
fun getSocksPort(): Int {
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
return parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
}
fun getHttpPort(): Int {
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
return parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
}
fun initAssets(context: Context, assets: AssetManager) {
val extFolder = Utils.userAssetPath(context)
try {
val geo = arrayOf("geosite.dat", "geoip.dat")
assets.list("")
?.filter { geo.contains(it) }
?.filter { !File(extFolder, it).exists() }
?.forEach {
val target = File(extFolder, it)
assets.open(it).use { input ->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
Log.i(
ANG_PACKAGE,
"Copied from apk assets folder to ${target.absolutePath}"
)
}
} catch (e: Exception) {
Log.e(ANG_PACKAGE, "asset copy failed", e)
}
}
}

View File

@@ -1,11 +1,11 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.DEFAULT_NETWORK
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
import com.v2ray.ang.AppConfig.DNS_GOOGLE_ADDRESSES
@@ -16,10 +16,11 @@ import com.v2ray.ang.AppConfig.DNS_PUB_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_PUB_DOMAIN
import com.v2ray.ang.AppConfig.GEOIP_CN
import com.v2ray.ang.AppConfig.GEOSITE_CN
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.GOOGLEAPIS_CN_DOMAIN
import com.v2ray.ang.AppConfig.GOOGLEAPIS_COM_DOMAIN
import com.v2ray.ang.AppConfig.HEADER_TYPE_HTTP
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
@@ -29,32 +30,34 @@ import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.fmt.HttpFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt
import com.v2ray.ang.fmt.TrojanFmt
import com.v2ray.ang.fmt.VlessFmt
import com.v2ray.ang.fmt.VmessFmt
import com.v2ray.ang.fmt.WireguardFmt
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
object V2rayConfigUtil {
object V2rayConfigManager {
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
if (config.configType == EConfigType.CUSTOM) {
val raw = MmkvManager.decodeServerRaw(guid)
val customConfig = if (raw.isNullOrBlank()) {
config.fullConfig?.toPrettyPrinting() ?: return ConfigResult(false)
} else {
raw
}
val domainPort = config.getProxyOutbound()?.getServerAddressAndPort()
return ConfigResult(true, guid, customConfig, domainPort)
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
val domainPort = config.getServerAddressAndPort()
return ConfigResult(true, guid, raw, domainPort)
}
val result = getV2rayNonCustomConfig(context, config)
//Log.d(ANG_PACKAGE, result.content)
Log.d(ANG_PACKAGE, result.content)
result.guid = guid
return result
} catch (e: Exception) {
@@ -63,11 +66,10 @@ object V2rayConfigUtil {
}
}
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): ConfigResult {
private fun getV2rayNonCustomConfig(context: Context, config: ProfileItem): ConfigResult {
val result = ConfigResult(false)
val outbound = config.getProxyOutbound() ?: return result
val address = outbound.getServerAddress() ?: return result
val address = config.server ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
@@ -82,14 +84,13 @@ object V2rayConfigUtil {
}
val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
v2rayConfig.log.loglevel =
settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
inbounds(v2rayConfig)
val isPlugin = outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)
val retOut = outbounds(v2rayConfig, outbound, isPlugin)
val isPlugin = config.configType == EConfigType.HYSTERIA2
val retOut = outbounds(v2rayConfig, config, isPlugin) ?: return result
val retMore = moreOutbounds(v2rayConfig, config.subscriptionId, isPlugin)
routing(v2rayConfig)
@@ -98,10 +99,10 @@ object V2rayConfigUtil {
dns(v2rayConfig)
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
customLocalDns(v2rayConfig)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
v2rayConfig.policy = null
}
@@ -118,20 +119,18 @@ object V2rayConfigUtil {
val httpPort = SettingsManager.getHttpPort()
v2rayConfig.inbounds.forEach { curInbound ->
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING) != true) {
//bind all inbounds to localhost if the user requests
curInbound.listen = LOOPBACK
}
}
v2rayConfig.inbounds[0].port = socksPort
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
?: false
val fakedns = MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
val sniffAllTlsAndHttp =
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
?: true
MmkvManager.decodeSettingsBool(AppConfig.PREF_SNIFFING_ENABLED, true) != false
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
v2rayConfig.inbounds[0].sniffing?.routeOnly =
settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
MmkvManager.decodeSettingsBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
if (!sniffAllTlsAndHttp) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
}
@@ -154,11 +153,7 @@ object V2rayConfigUtil {
return true
}
private fun outbounds(
v2rayConfig: V2rayConfig,
outbound: V2rayConfig.OutboundBean,
isPlugin: Boolean
): Pair<Boolean, String> {
private fun outbounds(v2rayConfig: V2rayConfig, config: ProfileItem, isPlugin: Boolean): Pair<Boolean, String>? {
if (isPlugin) {
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
val outboundNew = V2rayConfig.OutboundBean(
@@ -181,8 +176,9 @@ object V2rayConfigUtil {
return Pair(true, outboundNew.getServerAddressAndPort())
}
val outbound = getProxyOutbound(config) ?: return null
val ret = updateOutboundWithGlobalSettings(outbound)
if (!ret) return Pair(false, "")
if (!ret) return null
if (v2rayConfig.outbounds.isNotEmpty()) {
v2rayConfig.outbounds[0] = outbound
@@ -191,12 +187,12 @@ object V2rayConfigUtil {
}
updateOutboundFragment(v2rayConfig)
return Pair(true, outbound.getServerAddressAndPort())
return Pair(true, config.getServerAddressAndPort())
}
private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
&& MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
) {
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
}
@@ -206,7 +202,7 @@ object V2rayConfigUtil {
try {
v2rayConfig.routing.domainStrategy =
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
MmkvManager.decodeSettingsString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch"
val rulesetItems = MmkvManager.decodeRoutingRulesets()
@@ -256,7 +252,7 @@ object V2rayConfigUtil {
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf(GEOSITE_CN)
val proxyDomain = userRule2Domain(TAG_PROXY)
val directDomain = userRule2Domain(TAG_DIRECT)
@@ -280,7 +276,7 @@ object V2rayConfigUtil {
)
val localDnsPort = Utils.parseInt(
settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT),
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
)
v2rayConfig.inbounds.add(
@@ -337,10 +333,10 @@ object V2rayConfigUtil {
if (proxyDomain.size > 0) {
servers.add(
V2rayConfig.DnsBean.ServersBean(
remoteDns.first(),
53,
proxyDomain,
null
address = remoteDns.first(),
port = 53,
domains = proxyDomain,
expectIPs = null
)
)
}
@@ -353,10 +349,11 @@ object V2rayConfigUtil {
if (directDomain.size > 0) {
servers.add(
V2rayConfig.DnsBean.ServersBean(
domesticDns.first(),
53,
directDomain,
if (isCnRoutingMode) geoipCn else null
address = domesticDns.first(),
port = 53,
domains = directDomain,
expectIPs = if (isCnRoutingMode) geoipCn else null,
skipFallback = true
)
)
}
@@ -414,7 +411,7 @@ object V2rayConfigUtil {
private fun updateOutboundWithGlobalSettings(outbound: V2rayConfig.OutboundBean): Boolean {
try {
var muxEnabled = settingsStorage?.decodeBool(AppConfig.PREF_MUX_ENABLED, false)
var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
val protocol = outbound.protocol
if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
@@ -432,11 +429,11 @@ object V2rayConfigUtil {
if (muxEnabled == true) {
outbound.mux?.enabled = true
outbound.mux?.concurrency =
settingsStorage?.decodeInt(AppConfig.PREF_MUX_CONCURRENCY) ?: 8
MmkvManager.decodeSettingsInt(AppConfig.PREF_MUX_CONCURRENCY, 8)
outbound.mux?.xudpConcurrency =
settingsStorage?.decodeInt(AppConfig.PREF_MUX_XUDP_CONCURRENCY) ?: 8
MmkvManager.decodeSettingsInt(AppConfig.PREF_MUX_XUDP_CONCURRENCY, 16)
outbound.mux?.xudpProxyUDP443 =
settingsStorage?.decodeString(AppConfig.PREF_MUX_XUDP_QUIC) ?: "reject"
MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_QUIC) ?: "reject"
} else {
outbound.mux?.enabled = false
outbound.mux?.concurrency = -1
@@ -448,20 +445,20 @@ object V2rayConfigUtil {
} else {
outbound.settings?.address as List<*>
}
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) != true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) != true) {
localTunAddr = listOf(localTunAddr.first())
}
outbound.settings?.address = localTunAddr
}
if (outbound.streamSettings?.network == DEFAULT_NETWORK
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP
&& outbound.streamSettings?.tcpSettings?.header?.type == HEADER_TYPE_HTTP
) {
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
val requestString: String by lazy {
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
"""{"version":"1.1","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.122 Mobile Safari/537.36"Safari/537.36,"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
}
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
requestString,
@@ -486,11 +483,11 @@ object V2rayConfigUtil {
private fun updateOutboundFragment(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == false) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == false) {
return true
}
if (v2rayConfig.outbounds[0].streamSettings?.security != V2rayConfig.TLS
&& v2rayConfig.outbounds[0].streamSettings?.security != V2rayConfig.REALITY
if (v2rayConfig.outbounds[0].streamSettings?.security != AppConfig.TLS
&& v2rayConfig.outbounds[0].streamSettings?.security != AppConfig.REALITY
) {
return true
}
@@ -503,12 +500,12 @@ object V2rayConfigUtil {
)
var packets =
settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
if (v2rayConfig.outbounds[0].streamSettings?.security == AppConfig.REALITY
&& packets == "tlshello"
) {
packets = "1-3"
} else if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.TLS
} else if (v2rayConfig.outbounds[0].streamSettings?.security == AppConfig.TLS
&& packets != "tlshello"
) {
packets = "tlshello"
@@ -517,16 +514,16 @@ object V2rayConfigUtil {
fragmentOutbound.settings = V2rayConfig.OutboundBean.OutSettingsBean(
fragment = V2rayConfig.OutboundBean.OutSettingsBean.FragmentBean(
packets = packets,
length = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_LENGTH)
length = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_LENGTH)
?: "50-100",
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
interval = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_INTERVAL)
?: "10-20"
),
noises = listOf(
V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
type = "rand",
packet = "100-200",
delay = "10-20",
packet = "10-20",
delay = "10-16",
)
),
)
@@ -562,7 +559,7 @@ object V2rayConfigUtil {
return returnPair
}
//fragment proxy
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
return returnPair
}
@@ -578,7 +575,7 @@ object V2rayConfigUtil {
//Previous proxy
val prevNode = SettingsManager.getServerViaRemarks(subItem.prevProfile)
if (prevNode != null) {
val prevOutbound = prevNode.getProxyOutbound()
val prevOutbound = getProxyOutbound(prevNode)
if (prevOutbound != null) {
updateOutboundWithGlobalSettings(prevOutbound)
prevOutbound.tag = TAG_PROXY + "2"
@@ -587,14 +584,14 @@ object V2rayConfigUtil {
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
dialerProxy = prevOutbound.tag
)
domainPort = prevOutbound.getServerAddressAndPort()
domainPort = prevNode.getServerAddressAndPort()
}
}
//Next proxy
val nextNode = SettingsManager.getServerViaRemarks(subItem.nextProfile)
if (nextNode != null) {
val nextOutbound = nextNode.getProxyOutbound()
val nextOutbound = getProxyOutbound(nextNode)
if (nextOutbound != null) {
updateOutboundWithGlobalSettings(nextOutbound)
nextOutbound.tag = TAG_PROXY
@@ -616,4 +613,20 @@ object V2rayConfigUtil {
}
return returnPair
}
}
fun getProxyOutbound(profileItem: ProfileItem): V2rayConfig.OutboundBean? {
return when (profileItem.configType) {
EConfigType.VMESS -> VmessFmt.toOutbound(profileItem)
EConfigType.CUSTOM -> null
EConfigType.SHADOWSOCKS -> ShadowsocksFmt.toOutbound(profileItem)
EConfigType.SOCKS -> SocksFmt.toOutbound(profileItem)
EConfigType.VLESS -> VlessFmt.toOutbound(profileItem)
EConfigType.TROJAN -> TrojanFmt.toOutbound(profileItem)
EConfigType.WIREGUARD -> WireguardFmt.toOutbound(profileItem)
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toOutbound(profileItem)
EConfigType.HTTP -> HttpFmt.toOutbound(profileItem)
}
}
}

View File

@@ -29,8 +29,9 @@ class PluginList : ArrayList<Plugin>() {
init {
addAll(
AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA)
.filter { it.providerInfo.exported }.map { NativePlugin(it) })
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA
)
.filter { it.providerInfo.exported }.map { NativePlugin(it) })
}
val lookup = mutableMapOf<String, Plugin>().apply {
@@ -39,13 +40,13 @@ class PluginList : ArrayList<Plugin>() {
if (old != null && old != plugin) {
this@PluginList.remove(old)
}
/* if (old != null && old !== plugin) {
val packages = this@PluginList.filter { it.id == plugin.id }
.joinToString { it.packageName }
val message = "Conflicting plugins found from: $packages"
Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}*/
/* if (old != null && old !== plugin) {
val packages = this@PluginList.filter { it.id == plugin.id }
.joinToString { it.packageName }
val message = "Conflicting plugins found from: $packages"
Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}*/
}
check(put(plugin.id, plugin))
}

View File

@@ -43,6 +43,7 @@ import java.io.FileNotFoundException
object PluginManager {
class PluginNotFoundException(val plugin: String) : FileNotFoundException(plugin)
private var receiver: BroadcastReceiver? = null
private var cachedPlugins: PluginList? = null
fun fetchPlugins() = synchronized(this) {
@@ -88,30 +89,34 @@ object PluginManager {
flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
}
var providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "com.github.dyhkwong.AngApplication")), flags)
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "com.github.dyhkwong.AngApplication")), flags
)
.filter { it.providerInfo.exported }
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "io.nekohasekai.AngApplication")), flags)
.filter { it.providerInfo.exported }
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "io.nekohasekai.AngApplication")), flags
)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "moe.matsuri.lite")), flags)
.filter { it.providerInfo.exported }
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "moe.matsuri.lite")), flags
)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "fr.husi")), flags)
.filter { it.providerInfo.exported }
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "fr.husi")), flags
)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA
).filter {
it.providerInfo.exported &&
it.providerInfo.metaData.containsKey(METADATA_KEY_ID) &&
it.providerInfo.metaData.getString(METADATA_KEY_ID) == pluginId
it.providerInfo.metaData.containsKey(METADATA_KEY_ID) &&
it.providerInfo.metaData.getString(METADATA_KEY_ID) == pluginId
}
if (providers.size > 1) {
providers = listOf(providers[0]) // What if there is more than one?
@@ -129,7 +134,7 @@ object PluginManager {
try {
initNativeFaster(provider)?.also { return InitResult(it) }
} catch (t: Throwable) {
// Logs.w("Initializing native plugin faster mode failed")
// Logs.w("Initializing native plugin faster mode failed")
failure = t
}
@@ -138,19 +143,23 @@ object PluginManager {
authority(provider.authority)
}.build()
try {
return initNativeFast(AngApplication.application.contentResolver,
return initNativeFast(
AngApplication.application.contentResolver,
pluginId,
uri)?.let { InitResult(it) }
uri
)?.let { InitResult(it) }
} catch (t: Throwable) {
// Logs.w("Initializing native plugin fast mode failed")
// Logs.w("Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}
try {
return initNativeSlow(AngApplication.application.contentResolver,
return initNativeSlow(
AngApplication.application.contentResolver,
pluginId,
uri)?.let { InitResult(it) }
uri
)?.let { InitResult(it) }
} catch (t: Throwable) {
failure?.also { t.addSuppressed(it) }
throw t
@@ -180,11 +189,13 @@ object PluginManager {
throw IndexOutOfBoundsException("Plugin entry binary not found")
val pluginDir = File(AngApplication.application.noBackupFilesDir, "plugin")
(cr.query(uri,
(cr.query(
uri,
arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE),
null,
null,
null)
null
)
?: return null).use { cursor ->
if (!cursor.moveToFirst()) entryNotFound()
pluginDir.deleteRecursively()
@@ -197,11 +208,13 @@ object PluginManager {
cr.openInputStream(uri.buildUpon().path(path).build())!!.use { inStream ->
file.outputStream().use { outStream -> inStream.copyTo(outStream) }
}
Os.chmod(file.absolutePath, when (cursor.getType(1)) {
Cursor.FIELD_TYPE_INTEGER -> cursor.getInt(1)
Cursor.FIELD_TYPE_STRING -> cursor.getString(1).toInt(8)
else -> throw IllegalArgumentException("File mode should be of type int")
})
Os.chmod(
file.absolutePath, when (cursor.getType(1)) {
Cursor.FIELD_TYPE_INTEGER -> cursor.getInt(1)
Cursor.FIELD_TYPE_STRING -> cursor.getString(1).toInt(8)
else -> throw IllegalArgumentException("File mode should be of type int")
}
)
if (path == pluginId) initialized = true
} while (cursor.moveToNext())
}
@@ -213,6 +226,7 @@ object PluginManager {
is String -> value
is Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
.getString(value)
null -> null
else -> error("meta-data $key has invalid type ${value.javaClass}")
}

View File

@@ -3,8 +3,8 @@ package com.v2ray.ang.receiver
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.util.MmkvManager
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {

View File

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

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.launch
class ProcessService {
private val TAG = ANG_PACKAGE
private lateinit var process: Process
private var process: Process? = null
fun runProcess(context: Context, cmd: MutableList<String>) {
Log.d(TAG, cmd.toString())
@@ -24,7 +24,7 @@ class ProcessService {
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(50L)
Log.d(TAG, "runProcess check")
process.waitFor()
process?.waitFor()
Log.d(TAG, "runProcess exited")
}
Log.d(TAG, process.toString())
@@ -42,4 +42,4 @@ class ProcessService {
Log.d(TAG, e.toString())
}
}
}
}

View File

@@ -37,7 +37,7 @@ class QSTileService : TileService() {
setState(Tile.STATE_INACTIVE)
mMsgReceive = ReceiveMessageHandler(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), Context.RECEIVER_EXPORTED)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), RECEIVER_EXPORTED)
} else {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
}

View File

@@ -14,10 +14,8 @@ 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.util.AngConfigManager
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.handler.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.handler.MmkvManager
object SubscriptionUpdater {

View File

@@ -18,16 +18,15 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
@@ -55,7 +54,7 @@ object V2RayServiceManager {
Seq.setContext(value?.get()?.getService()?.applicationContext)
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
}
var currentConfig: ServerConfig? = null
var currentConfig: ProfileItem? = null
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
@@ -65,15 +64,17 @@ object V2RayServiceManager {
fun startV2Ray(context: Context) {
if (v2rayPoint.isRunning) return
val guid = MmkvManager.getSelectServer() ?: return
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
if (!result.status) return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (!Utils.isValidUrl(config.server) && !Utils.isValidUrl(config.server)) return
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
// if (!result.status) return
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING) == true) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
} else {
context.toast(R.string.toast_services_start)
}
val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
val intent = if ((MmkvManager.decodeSettingsString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
Intent(context.applicationContext, V2RayVpnService::class.java)
} else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -132,7 +133,7 @@ object V2RayServiceManager {
if (v2rayPoint.isRunning) {
return
}
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
val result = V2rayConfigManager.getV2rayConfig(service, guid)
if (!result.status)
return
@@ -155,7 +156,7 @@ object V2RayServiceManager {
currentConfig = config
try {
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true)
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
@@ -378,7 +379,7 @@ object V2RayServiceManager {
private fun startSpeedNotification() {
if (mDisposable == null &&
v2rayPoint.isRunning &&
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true
MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) == true
) {
var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags()

View File

@@ -8,12 +8,12 @@ import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
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.V2rayConfigManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -56,12 +56,12 @@ class V2RayTestService : Service() {
private fun startRealPing(guid: String): Long {
val retFailure = -1L
val server = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (server.getProxyOutbound()?.protocol?.equals(EConfigType.HYSTERIA2.name, true) == true) {
val delay = PluginUtil.realPingHy2(this, server)
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (config.configType == EConfigType.HYSTERIA2) {
val delay = PluginUtil.realPingHy2(this, config)
return delay
} else {
val config = V2rayConfigUtil.getV2rayConfig(this, guid)
val config = V2rayConfigManager.getV2rayConfig(this, guid)
if (!config.status) {
return retFailure
}

View File

@@ -20,9 +20,9 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -64,7 +64,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
.build()
}
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val connectivity by lazy { getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager }
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkCallback by lazy {
@@ -130,7 +130,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.addRoute("0.0.0.0", 0)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
if (bypassLan) {
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
@@ -139,7 +139,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
// if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
// } else {
Utils.getVpnDnsServers()
@@ -153,9 +153,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
val selfPackageName = BuildConfig.APPLICATION_ID
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS) == true
//process self package
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
apps?.forEach {
@@ -215,12 +215,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
"--loglevel", "notice"
)
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw")
cmd.add("$LOOPBACK:${localDnsPort}")
}

View File

@@ -34,11 +34,11 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityMainBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
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.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@@ -90,7 +90,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
} else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
} else if ((MmkvManager.decodeSettingsString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
val intent = VpnService.prepare(this)
if (intent == null) {
startV2Ray()
@@ -126,6 +126,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
initGroupTab()
setupViewModel()
migrateLegacy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RxPermissions(this)
@@ -172,7 +173,22 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
mainViewModel.startListenBroadcast()
mainViewModel.copyAssets(assets)
mainViewModel.initAssets(assets)
}
private fun migrateLegacy() {
lifecycleScope.launch(Dispatchers.IO) {
val result = MigrateManager.migrateServerConfig2Profile()
launch(Dispatchers.Main) {
if (result) {
toast(getString(R.string.migration_success))
mainViewModel.reloadServerList()
} else {
//toast(getString(R.string.migration_fail))
}
}
}
}
private fun initGroupTab() {
@@ -504,6 +520,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toast(R.string.toast_success)
mainViewModel.reloadServerList()
}
countSub > 0 -> initGroupTab()
else -> toast(R.string.toast_failure)
}
@@ -606,10 +623,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
//toast(R.string.toast_success)
toast(R.string.connection_test_testing)
toast(R.string.toast_success)
mainViewModel.reloadServerList()
mainViewModel.testAllRealPing()
} else {
toast(R.string.toast_failure)
}

View File

@@ -17,12 +17,11 @@ import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
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.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
@@ -130,6 +129,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemMainBinding.layoutEdit.setOnClickListener {
val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", isRunning)
.putExtra("createConfigType", profile.configType.value)
if (profile.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
@@ -138,7 +138,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
holder.itemMainBinding.layoutRemove.setOnClickListener {
if (guid != MmkvManager.getSelectServer()) {
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
removeServer(guid, position)

View File

@@ -17,8 +17,8 @@ import com.v2ray.ang.databinding.ActivityBypassListBinding
import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
@@ -41,7 +41,7 @@ class PerAppProxyActivity : BaseActivity() {
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
binding.recyclerView.addItemDecoration(dividerItemDecoration)
val blacklist = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io())
@@ -132,14 +132,14 @@ class PerAppProxyActivity : BaseActivity() {
***/
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY, isChecked)
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked)
}
binding.switchPerAppProxy.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
binding.switchPerAppProxy.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked ->
settingsStorage.encode(AppConfig.PREF_BYPASS_APPS, isChecked)
MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked)
}
binding.switchBypassApps.isChecked = settingsStorage.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false)
/***
et_search.setOnEditorActionListener { v, actionId, event ->
@@ -175,7 +175,7 @@ class PerAppProxyActivity : BaseActivity() {
override fun onPause() {
super.onPause()
adapter?.let {
settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist)
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist)
}
}
@@ -215,7 +215,7 @@ class PerAppProxyActivity : BaseActivity() {
}
it.notifyDataSetChanged()
true
} ?: false
} == true
R.id.select_proxy_app -> {
selectProxyApp()

View File

@@ -1,7 +1,6 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
@@ -10,7 +9,7 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingEditBinding
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -38,7 +37,7 @@ class RoutingEditActivity : BaseActivity() {
private fun bindingServer(rulesetItem: RulesetItem): Boolean {
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
binding.chkLocked.isChecked = rulesetItem.looked ?: false
binding.chkLocked.isChecked = rulesetItem.looked == true
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
binding.etPort.text = Utils.getEditable(rulesetItem.port)

View File

@@ -10,17 +10,15 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -52,14 +50,14 @@ class RoutingSettingActivity : BaseActivity() {
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
val found = Utils.arrayFind(routing_domain_strategy, settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "")
val found = Utils.arrayFind(routing_domain_strategy, MmkvManager.decodeSettingsString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "")
found.let { binding.spDomainStrategy.setSelection(if (it >= 0) it else 0) }
binding.spDomainStrategy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
settingsStorage.encode(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, routing_domain_strategy[position])
MmkvManager.encodeSettings(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, routing_domain_strategy[position])
}
}
}

View File

@@ -8,10 +8,9 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.databinding.ItemRecyclerRoutingSettingBinding
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.ui.MainRecyclerAdapter.BaseViewHolder
import com.v2ray.ang.util.SettingsManager
class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : RecyclerView.Adapter<RoutingSettingRecyclerAdapter.MainViewHolder>(),
ItemTouchHelperAdapter {
@@ -26,7 +25,7 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.looked ?: false
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.looked == true
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener {
@@ -37,7 +36,7 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
}
holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if( !it.isPressed) return@setOnCheckedChangeListener
if (!it.isPressed) return@setOnCheckedChangeListener
ruleset.enabled = isChecked
SettingsManager.saveRoutingRuleset(position, ruleset)
}

View File

@@ -7,7 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.handler.AngConfigManager
class ScScannerActivity : BaseActivity() {

View File

@@ -12,7 +12,7 @@ import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder
import io.github.g00fy2.quickie.QRResult
import io.github.g00fy2.quickie.ScanCustomCode
@@ -25,7 +25,7 @@ class ScannerActivity : BaseActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
launchScan()
}
}

View File

@@ -2,6 +2,7 @@ package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
@@ -13,22 +14,21 @@ import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.DEFAULT_PORT
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
import com.v2ray.ang.AppConfig.REALITY
import com.v2ray.ang.AppConfig.TLS
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.Utils.getIpv6Address
class ServerActivity : BaseActivity() {
@@ -87,7 +87,6 @@ class ServerActivity : BaseActivity() {
private val et_address: EditText by lazy { findViewById(R.id.et_address) }
private val et_port: EditText by lazy { findViewById(R.id.et_port) }
private val et_id: EditText by lazy { findViewById(R.id.et_id) }
private val et_alterId: EditText? by lazy { findViewById(R.id.et_alterId) }
private val et_security: EditText? by lazy { findViewById(R.id.et_security) }
private val sp_flow: Spinner? by lazy { findViewById(R.id.sp_flow) }
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
@@ -114,11 +113,12 @@ class ServerActivity : BaseActivity() {
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_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) }
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
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) }
private val et_obfs_password: EditText? by lazy { findViewById(R.id.et_obfs_password) }
private val et_port_hop: EditText? by lazy { findViewById(R.id.et_port_hop) }
private val et_port_hop_interval: EditText? by lazy { findViewById(R.id.et_port_hop_interval) }
private val et_pinsha256: EditText? by lazy { findViewById(R.id.et_pinsha256) }
override fun onCreate(savedInstanceState: Bundle?) {
@@ -153,12 +153,15 @@ class ServerActivity : BaseActivity() {
sp_header_type_title?.text = if (networks[position] == "grpc")
getString(R.string.server_lab_mode_type) else
getString(R.string.server_lab_head_type)
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
sp_header_type?.setSelection(Utils.arrayFind(types, transportDetails[0]))
et_request_host?.text = Utils.getEditable(transportDetails[1])
et_path?.text = Utils.getEditable(transportDetails[2])
config?.headerType?.let { it ->
sp_header_type?.setSelection(Utils.arrayFind(types, it))
}
config?.host?.let { it ->
et_request_host?.text = Utils.getEditable(it)
}
config?.path?.let { it ->
et_path?.text = Utils.getEditable(it)
}
tv_request_host?.text = Utils.getEditable(
getString(
when (networks[position]) {
@@ -243,7 +246,6 @@ class ServerActivity : BaseActivity() {
).forEach { it?.visibility = View.VISIBLE }
}
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {
@@ -260,120 +262,99 @@ class ServerActivity : BaseActivity() {
/**
* binding selected server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
val outbound = config.getProxyOutbound() ?: return false
private fun bindingServer(config: ProfileItem): Boolean {
et_remarks.text = Utils.getEditable(config.remarks)
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
et_port.text =
Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
et_alterId?.text =
Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
if (config.configType == EConfigType.SOCKS
|| config.configType == EConfigType.HTTP
) {
et_security?.text =
Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
et_address.text = Utils.getEditable(config.server.orEmpty())
et_port.text = Utils.getEditable(config.serverPort ?: DEFAULT_PORT.toString())
et_id.text = Utils.getEditable(config.password.orEmpty())
if (config.configType == EConfigType.SOCKS || config.configType == EConfigType.HTTP) {
et_security?.text = Utils.getEditable(config.username.orEmpty())
} else if (config.configType == EConfigType.VLESS) {
et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
val flow = Utils.arrayFind(
flows,
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty()
)
et_security?.text = Utils.getEditable(config.method.orEmpty())
val flow = Utils.arrayFind(flows, config.flow.orEmpty())
if (flow >= 0) {
sp_flow?.setSelection(flow)
}
} else if (config.configType == EConfigType.WIREGUARD) {
et_public_key?.text =
Utils.getEditable(outbound.settings?.peers?.get(0)?.publicKey.orEmpty())
if (outbound.settings?.reserved == null) {
et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0")
et_id.text = Utils.getEditable(config.secretKey.orEmpty())
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
if (config.reserved == null) {
et_reserved1?.text = Utils.getEditable("0,0,0")
} else {
et_reserved1?.text =
Utils.getEditable(outbound.settings?.reserved?.get(0).toString())
et_reserved2?.text =
Utils.getEditable(outbound.settings?.reserved?.get(1).toString())
et_reserved3?.text =
Utils.getEditable(outbound.settings?.reserved?.get(2).toString())
et_reserved1?.text = Utils.getEditable(config.reserved?.toString())
}
if (outbound.settings?.address == null) {
et_local_address?.text =
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
if (config.localAddress == null) {
et_local_address?.text = Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
} else {
val list = outbound.settings?.address as List<*>
et_local_address?.text = Utils.getEditable(list.joinToString(","))
et_local_address?.text = Utils.getEditable(config.localAddress)
}
if (outbound.settings?.mtu == null) {
if (config.mtu == null) {
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
} else {
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
et_local_mtu?.text = Utils.getEditable(config.mtu.toString())
}
} else if (config.configType == EConfigType.HYSTERIA2) {
et_obfs_password?.text = Utils.getEditable(outbound.settings?.obfsPassword)
et_obfs_password?.text = Utils.getEditable(config.obfsPassword)
et_port_hop?.text = Utils.getEditable(config.portHopping)
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
}
val securityEncryptions =
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security =
Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security = Utils.arrayFind(securityEncryptions, config.method.orEmpty())
if (security >= 0) {
sp_security?.setSelection(security)
}
val streamSetting = config.outboundBean?.streamSettings ?: return true
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
val streamSecurity = Utils.arrayFind(streamSecuritys, config.security.orEmpty())
if (streamSecurity >= 0) {
sp_stream_security?.setSelection(streamSecurity)
(streamSetting.tlsSettings ?: streamSetting.realitySettings)?.let { tlsSetting ->
container_sni?.visibility = View.VISIBLE
container_fingerprint?.visibility = View.VISIBLE
container_alpn?.visibility = View.VISIBLE
et_sni?.text = Utils.getEditable(tlsSetting.serverName)
tlsSetting.fingerprint?.let {
val utlsIndex = Utils.arrayFind(uTlsItems, tlsSetting.fingerprint)
sp_stream_fingerprint?.setSelection(utlsIndex)
}
tlsSetting.alpn?.let {
val alpnIndex = Utils.arrayFind(
alpns,
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
)
sp_stream_alpn?.setSelection(alpnIndex)
}
if (streamSetting.tlsSettings != null) {
container_allow_insecure?.visibility = View.VISIBLE
val allowinsecure =
Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
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
} else { // reality settings
container_public_key?.visibility = View.VISIBLE
et_public_key?.text = Utils.getEditable(tlsSetting.publicKey.orEmpty())
container_short_id?.visibility = View.VISIBLE
et_short_id?.text = Utils.getEditable(tlsSetting.shortId.orEmpty())
container_spider_x?.visibility = View.VISIBLE
et_spider_x?.text = Utils.getEditable(tlsSetting.spiderX.orEmpty())
container_allow_insecure?.visibility = View.GONE
}
container_sni?.visibility = View.VISIBLE
container_fingerprint?.visibility = View.VISIBLE
container_alpn?.visibility = View.VISIBLE
et_sni?.text = Utils.getEditable(config.sni)
config.fingerPrint?.let {
val utlsIndex = Utils.arrayFind(uTlsItems, it)
sp_stream_fingerprint?.setSelection(utlsIndex)
}
if (streamSetting.tlsSettings == null && streamSetting.realitySettings == null) {
container_sni?.visibility = View.GONE
container_fingerprint?.visibility = View.GONE
container_alpn?.visibility = View.GONE
container_allow_insecure?.visibility = View.GONE
config.alpn?.let {
val alpnIndex = Utils.arrayFind(alpns, it)
sp_stream_alpn?.setSelection(alpnIndex)
}
if (config.security == TLS) {
container_allow_insecure?.visibility = View.VISIBLE
val allowinsecure = Utils.arrayFind(allowinsecures, config.insecure.toString())
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
} else if (config.security == REALITY) { // reality settings
container_public_key?.visibility = View.VISIBLE
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
container_short_id?.visibility = View.VISIBLE
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_allow_insecure?.visibility = View.GONE
}
}
val network = Utils.arrayFind(networks, streamSetting.network)
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
}
val network = Utils.arrayFind(networks, config.network.orEmpty())
if (network >= 0) {
sp_network?.setSelection(network)
}
@@ -388,7 +369,6 @@ class ServerActivity : BaseActivity() {
et_address.text = null
et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
et_id.text = null
et_alterId?.text = Utils.getEditable("0")
sp_security?.setSelection(0)
sp_network?.setSelection(0)
@@ -402,9 +382,7 @@ class ServerActivity : BaseActivity() {
//et_security.text = null
sp_flow?.setSelection(0)
et_public_key?.text = null
et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0")
et_reserved1?.text = Utils.getEditable("0,0,0")
et_local_address?.text =
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
@@ -423,13 +401,13 @@ class ServerActivity : BaseActivity() {
toast(R.string.server_lab_address)
return false
}
val port = Utils.parseInt(et_port.text.toString())
if (port <= 0) {
toast(R.string.server_lab_port)
return false
if (createConfigType != EConfigType.HYSTERIA2) {
if (Utils.parseInt(et_port.text.toString()) <= 0) {
toast(R.string.server_lab_port)
return false
}
}
val config =
MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(createConfigType)
if (config.configType != EConfigType.SOCKS
&& config.configType != EConfigType.HTTP
&& TextUtils.isEmpty(et_id.text.toString())
@@ -450,125 +428,73 @@ class ServerActivity : BaseActivity() {
return false
}
}
et_alterId?.let {
val alterId = Utils.parseInt(it.text.toString())
if (alterId < 0) {
toast(R.string.server_lab_alterid)
return false
}
}
config.remarks = et_remarks.text.toString().trim()
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
saveVnext(vnext, port, config)
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
saveServers(server, port, config)
}
val wireguard = config.outboundBean?.settings
wireguard?.peers?.get(0)?.let { _ ->
savePeer(wireguard, port)
}
saveCommon(config)
saveStreamSettings(config)
saveTls(config)
config.outboundBean?.streamSettings?.let {
val sni = saveStreamSettings(it)
saveTls(it, sni)
}
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId.orEmpty()
}
if (config.configType == EConfigType.HYSTERIA2) {
config.outboundBean?.settings?.obfsPassword = et_obfs_password?.text?.toString()
}
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config))
MmkvManager.encodeServerConfig(editGuid, config)
toast(R.string.toast_success)
finish()
return true
}
private fun saveVnext(
vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean,
port: Int,
config: ServerConfig
) {
vnext.address = et_address.text.toString().trim()
vnext.port = port
vnext.users[0].id = et_id.text.toString().trim()
private fun saveCommon(config: ProfileItem) {
config.remarks = et_remarks.text.toString().trim()
config.server = et_address.text.toString().trim()
config.serverPort = et_port.text.toString().trim()
config.password = et_id.text.toString().trim()
if (config.configType == EConfigType.VMESS) {
vnext.users[0].alterId = Utils.parseInt(et_alterId?.text.toString())
vnext.users[0].security = securitys[sp_security?.selectedItemPosition ?: 0]
config.method = securitys[sp_security?.selectedItemPosition ?: 0]
} else if (config.configType == EConfigType.VLESS) {
vnext.users[0].encryption = et_security?.text.toString().trim()
vnext.users[0].flow = flows[sp_flow?.selectedItemPosition ?: 0]
}
}
private fun saveServers(
server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean,
port: Int,
config: ServerConfig
) {
server.address = et_address.text.toString().trim()
server.port = port
if (config.configType == EConfigType.SHADOWSOCKS) {
server.password = et_id.text.toString().trim()
server.method = shadowsocksSecuritys[sp_security?.selectedItemPosition ?: 0]
config.method = et_security?.text.toString().trim()
config.flow = flows[sp_flow?.selectedItemPosition ?: 0]
} else if (config.configType == EConfigType.SHADOWSOCKS) {
config.method = shadowsocksSecuritys[sp_security?.selectedItemPosition ?: 0]
} else if (config.configType == EConfigType.SOCKS || config.configType == EConfigType.HTTP) {
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
server.users = null
} else {
val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = et_security?.text.toString().trim()
socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean)
if (!TextUtils.isEmpty(et_security?.text) || !TextUtils.isEmpty(et_id.text)) {
config.username = et_security?.text.toString().trim()
}
} else if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.HYSTERIA2) {
server.password = et_id.text.toString().trim()
} else if (config.configType == EConfigType.TROJAN) {
} else if (config.configType == EConfigType.WIREGUARD) {
config.secretKey = et_id.text.toString().trim()
config.publicKey = et_public_key?.text.toString().trim()
config.reserved = et_reserved1?.text.toString().trim()
config.localAddress = et_local_address?.text.toString().trim()
config.mtu = Utils.parseInt(et_local_mtu?.text.toString())
} else if (config.configType == EConfigType.HYSTERIA2) {
config.obfsPassword = et_obfs_password?.text?.toString()
config.portHopping = et_port_hop?.text?.toString()
config.portHoppingInterval = et_port_hop_interval?.text?.toString()
config.pinSHA256 = et_pinsha256?.text?.toString()
}
}
private fun savePeer(wireguard: V2rayConfig.OutboundBean.OutSettingsBean, port: Int) {
wireguard.secretKey = et_id.text.toString().trim()
wireguard.peers?.get(0)?.publicKey = et_public_key?.text.toString().trim()
wireguard.peers?.get(0)?.endpoint =
getIpv6Address(et_address.text.toString().trim()) + ":" + port
val reserved1 = Utils.parseInt(et_reserved1?.text.toString())
val reserved2 = Utils.parseInt(et_reserved2?.text.toString())
val reserved3 = Utils.parseInt(et_reserved3?.text.toString())
if (reserved1 > 0 || reserved2 > 0 || reserved3 > 0) {
wireguard.reserved = listOf(reserved1, reserved2, reserved3)
} else {
wireguard.reserved = null
}
wireguard.address = et_local_address?.text.toString().removeWhiteSpace().split(",")
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
private fun saveStreamSettings(profileItem: ProfileItem) {
val network = sp_network?.selectedItemPosition ?: return
val type = sp_header_type?.selectedItemPosition ?: return
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
val path = et_path?.text?.toString()?.trim() ?: return
profileItem.network = networks[network]
profileItem.headerType = transportTypes(networks[network])[type]
profileItem.host = requestHost
profileItem.path = path
profileItem.seed = path
profileItem.quicSecurity = requestHost
profileItem.quicKey = path
profileItem.mode = transportTypes(networks[network])[type]
profileItem.serviceName = path
profileItem.authority = requestHost
}
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): String? {
val network = sp_network?.selectedItemPosition ?: return null
val type = sp_header_type?.selectedItemPosition ?: return null
val requestHost = et_request_host?.text?.toString()?.trim() ?: return null
val path = et_path?.text?.toString()?.trim() ?: return null
val sni = streamSetting.populateTransportSettings(
transport = networks[network],
headerType = transportTypes(networks[network])[type],
host = requestHost,
path = path,
seed = path,
quicSecurity = requestHost,
key = path,
mode = transportTypes(networks[network])[type],
serviceName = path,
authority = requestHost,
)
return sni
}
private fun saveTls(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, sni: String?) {
private fun saveTls(config: ProfileItem) {
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
val sniField = et_sni?.text?.toString()?.trim()
val allowInsecureField = sp_allow_insecure?.selectedItemPosition
@@ -580,21 +506,19 @@ class ServerActivity : BaseActivity() {
val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE) == true
} else {
allowinsecures[allowInsecureField].toBoolean()
}
streamSetting.populateTlsSettings(
streamSecurity = streamSecuritys[streamSecurity],
allowInsecure = allowInsecure,
sni = sniField ?: sni ?: "",
fingerprint = uTlsItems[utlsIndex],
alpns = alpns[alpnIndex],
publicKey = publicKey,
shortId = shortId,
spiderX = spiderX
)
config.security = streamSecuritys[streamSecurity]
config.insecure = allowInsecure
config.sni = sniField
config.fingerPrint = uTlsItems[utlsIndex]
config.alpn = alpns[alpnIndex]
config.publicKey = publicKey
config.shortId = shortId
config.spiderX = spiderX
}
private fun transportTypes(network: String?): Array<out String> {
@@ -618,12 +542,12 @@ class ServerActivity : BaseActivity() {
}
/**
* save server config
* delete server config
*/
private fun deleteServer(): Boolean {
if (editGuid.isNotEmpty()) {
if (editGuid != MmkvManager.getSelectServer()) {
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeServer(editGuid)

View File

@@ -8,15 +8,14 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.editorkit.utils.EditorTheme
import com.blacksquircle.ui.language.json.JsonLanguage
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import me.drakeet.support.toast.ToastCompat
@@ -50,10 +49,10 @@ class ServerCustomConfigActivity : BaseActivity() {
/**
* Binding selected server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
private fun bindingServer(config: ProfileItem): Boolean {
binding.etRemarks.text = Utils.getEditable(config.remarks)
val raw = MmkvManager.decodeServerRaw(editGuid)
val configContent = raw ?: config.fullConfig?.toPrettyPrinting().orEmpty()
val configContent = raw.orEmpty()
binding.editor.setTextContent(Utils.getEditable(configContent))
return true
@@ -84,9 +83,8 @@ class ServerCustomConfigActivity : BaseActivity() {
return false
}
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(EConfigType.CUSTOM)
config.remarks = if (binding.etRemarks.text.isNullOrEmpty()) v2rayConfig.remarks.orEmpty() else binding.etRemarks.text.toString()
config.fullConfig = v2rayConfig
MmkvManager.encodeServerConfig(editGuid, config)
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())

View File

@@ -17,8 +17,8 @@ import com.v2ray.ang.AppConfig
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.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel
import java.util.concurrent.TimeUnit
@@ -172,33 +172,33 @@ class SettingsActivity : BaseActivity() {
override fun onStart() {
super.onStart()
updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, VPN))
localDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
fakeDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FAKE_DNS_ENABLED, false)
localDnsPort?.summary = settingsStorage.decodeString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
vpnDns?.summary = settingsStorage.decodeString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
updateMode(MmkvManager.decodeSettingsString(AppConfig.PREF_MODE, VPN))
localDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
fakeDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED, false)
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
updateMux(settingsStorage.getBoolean(AppConfig.PREF_MUX_ENABLED, false))
mux?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_MUX_ENABLED, false)
muxConcurrency?.summary = settingsStorage.decodeString(AppConfig.PREF_MUX_CONCURRENCY, "8")
muxXudpConcurrency?.summary = settingsStorage.decodeString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false))
mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
muxConcurrency?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_CONCURRENCY, "8")
muxXudpConcurrency?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
updateFragment(settingsStorage.getBoolean(AppConfig.PREF_FRAGMENT_ENABLED, false))
fragment?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FRAGMENT_ENABLED, false)
fragmentPackets?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello")
fragmentLength?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")
fragmentInterval?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
updateFragment(MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false))
fragment?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false)
fragmentPackets?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello")
fragmentLength?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")
fragmentInterval?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
autoUpdateCheck?.isChecked = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
autoUpdateCheck?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
autoUpdateInterval?.summary =
settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
autoUpdateInterval?.isEnabled = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
MmkvManager.decodeSettingsString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
autoUpdateInterval?.isEnabled = MmkvManager.decodeSettingsBool(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
socksPort?.summary = settingsStorage.decodeString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
delayTestUrl?.summary = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
socksPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
httpPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
remoteDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
domesticDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
initSharedPreference()
}
@@ -225,7 +225,7 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_SNIFFING_ENABLED,
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
settingsStorage.decodeBool(key, true)
MmkvManager.decodeSettingsBool(key, true)
}
listOf(
@@ -240,7 +240,7 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_ALLOW_INSECURE
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
settingsStorage.decodeBool(key, false)
MmkvManager.decodeSettingsBool(key, false)
}
listOf(
@@ -252,8 +252,8 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_MODE
).forEach { key ->
if (settingsStorage.decodeString(key) != null) {
findPreference<ListPreference>(key)?.value = settingsStorage.decodeString(key)
if (MmkvManager.decodeSettingsString(key) != null) {
findPreference<ListPreference>(key)?.value = MmkvManager.decodeSettingsString(key)
}
}
}
@@ -261,14 +261,14 @@ class SettingsActivity : BaseActivity() {
private fun updateMode(mode: String?) {
val vpn = mode == VPN
perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
perAppProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
if (vpn) {
updateLocalDns(
settingsStorage.getBoolean(
MmkvManager.decodeSettingsBool(
AppConfig.PREF_LOCAL_DNS_ENABLED,
false
)
@@ -310,8 +310,8 @@ class SettingsActivity : BaseActivity() {
muxXudpConcurrency?.isEnabled = enabled
muxXudpQuic?.isEnabled = enabled
if (enabled) {
updateMuxConcurrency(settingsStorage.decodeString(AppConfig.PREF_MUX_CONCURRENCY, "8"))
updateMuxXudpConcurrency(settingsStorage.decodeString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8"))
updateMuxConcurrency(MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_CONCURRENCY, "8"))
updateMuxXudpConcurrency(MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8"))
}
}
@@ -336,9 +336,9 @@ class SettingsActivity : BaseActivity() {
fragmentLength?.isEnabled = enabled
fragmentInterval?.isEnabled = enabled
if (enabled) {
updateFragmentPackets(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello"))
updateFragmentLength(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100"))
updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
updateFragmentPackets(MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello"))
updateFragmentLength(MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100"))
updateFragmentInterval(MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
}
}

View File

@@ -10,7 +10,7 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

View File

@@ -13,9 +13,9 @@ import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View File

@@ -12,11 +12,11 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
@@ -45,7 +45,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
}
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if( !it.isPressed) return@setOnCheckedChangeListener
if (!it.isPressed) return@setOnCheckedChangeListener
subItem.enabled = isChecked
MmkvManager.encodeSubscription(subId, subItem)

View File

@@ -11,7 +11,7 @@ import android.widget.ListView
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityTaskerBinding
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.handler.MmkvManager
class TaskerActivity : BaseActivity() {
private val binding by lazy { ActivityTaskerBinding.inflate(layoutInflater) }

View File

@@ -7,7 +7,7 @@ import android.util.Log
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.handler.AngConfigManager
import java.net.URLDecoder
class UrlSchemeActivity : BaseActivity() {

View File

@@ -29,12 +29,12 @@ import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.extension.toTrafficString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
@@ -240,6 +240,15 @@ class UserAssetActivity : BaseActivity() {
return list + assets
}
fun initAssets() {
lifecycleScope.launch(Dispatchers.Default) {
SettingsManager.initAssets(this@UserAssetActivity, assets)
withContext(Dispatchers.Main) {
binding.recyclerView.adapter?.notifyDataSetChanged()
}
}
}
inner class UserAssetAdapter : RecyclerView.Adapter<UserAssetViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAssetViewHolder {
return UserAssetViewHolder(
@@ -271,10 +280,10 @@ class UserAssetActivity : BaseActivity() {
if (item.second.remarks in builtInGeoFiles && item.second.url == AppConfig.GeoUrl + item.second.remarks) {
holder.itemUserAssetBinding.layoutEdit.visibility = GONE
holder.itemUserAssetBinding.layoutRemove.visibility = GONE
//holder.itemUserAssetBinding.layoutRemove.visibility = GONE
} else {
holder.itemUserAssetBinding.layoutEdit.visibility = item.second.url.let { if (it == "file") GONE else VISIBLE }
holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE
//holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE
}
holder.itemUserAssetBinding.layoutEdit.setOnClickListener {
@@ -283,9 +292,16 @@ class UserAssetActivity : BaseActivity() {
startActivity(intent)
}
holder.itemUserAssetBinding.layoutRemove.setOnClickListener {
file?.delete()
MmkvManager.removeAssetUrl(item.first)
binding.recyclerView.adapter?.notifyItemRemoved(position)
AlertDialog.Builder(this@UserAssetActivity).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
file?.delete()
MmkvManager.removeAssetUrl(item.first)
initAssets()
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
}
}

View File

@@ -9,7 +9,7 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
import java.io.File

View File

@@ -5,30 +5,30 @@ import android.os.SystemClock
import android.util.Log
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.fmt.Hysteria2Fmt
import java.io.File
object PluginUtil {
//private const val HYSTERIA2 = "hysteria2-plugin"
private const val HYSTERIA2 = "libhysteria2.so"
private const val TAG = ANG_PACKAGE
private lateinit var procService: ProcessService
private val procService: ProcessService by lazy {
ProcessService()
}
// fun initPlugin(name: String): PluginManager.InitResult {
// return PluginManager.init(name)!!
// }
fun runPlugin(context: Context, config: ServerConfig?, domainPort: String?) {
fun runPlugin(context: Context, config: ProfileItem?, domainPort: String?) {
Log.d(TAG, "runPlugin")
val outbound = config?.getProxyOutbound() ?: return
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
val configFile = genConfigHy2(context, config, domainPort) ?: return
val cmd = genCmdHy2(context, configFile)
procService = ProcessService()
procService.runProcess(context, cmd)
}
}
@@ -37,12 +37,11 @@ object PluginUtil {
stopHy2()
}
fun realPingHy2(context: Context, config: ServerConfig?): Long {
fun realPingHy2(context: Context, config: ProfileItem?): Long {
Log.d(TAG, "realPingHy2")
val retFailure = -1L
val outbound = config?.getProxyOutbound() ?: return retFailure
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
val socksPort = Utils.findFreePort(listOf(0))
val configFile = genConfigHy2(context, config, "0:${socksPort}") ?: return retFailure
val cmd = genCmdHy2(context, configFile)
@@ -58,7 +57,7 @@ object PluginUtil {
return retFailure
}
private fun genConfigHy2(context: Context, config: ServerConfig, domainPort: String?): File? {
private fun genConfigHy2(context: Context, config: ProfileItem, domainPort: String?): File? {
Log.d(TAG, "runPlugin $HYSTERIA2")
val socksPort = domainPort?.split(":")?.last()
@@ -96,4 +95,4 @@ object PluginUtil {
Log.d(TAG, e.toString())
}
}
}
}

View File

@@ -24,8 +24,8 @@ import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.Language
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import java.io.IOException
import java.net.*
import java.util.*
@@ -130,7 +130,7 @@ object Utils {
* get remote dns servers from preference
*/
fun getRemoteDnsServers(): List<String> {
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_PROXY
val remoteDns = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_PROXY
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_PROXY)
@@ -139,7 +139,7 @@ object Utils {
}
fun getVpnDnsServers(): List<String> {
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS) ?: AppConfig.DNS_VPN
val vpnDns = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS) ?: AppConfig.DNS_VPN
return vpnDns.split(",").filter { isPureIpAddress(it) }
// allow empty, in that case dns will use system default
}
@@ -148,7 +148,7 @@ object Utils {
* get remote dns servers from preference
*/
fun getDomesticDnsServers(): List<String> {
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val domesticDns = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_DIRECT)
@@ -387,7 +387,7 @@ object Utils {
fun setNightMode(context: Context) {
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
when (MmkvManager.decodeSettingsString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
@@ -406,7 +406,7 @@ object Utils {
}
fun getLocale(): Locale {
val langCode = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
val langCode = MmkvManager.decodeSettingsString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
val language = Language.fromCode(langCode)
return when (language) {
@@ -422,8 +422,6 @@ object Utils {
}
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LocaleList.getDefault()[0]
} else {
@@ -453,7 +451,7 @@ object Utils {
return if (second) {
AppConfig.DelayTestUrl2
} else {
settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
}
}

View File

@@ -31,7 +31,7 @@ object ZipUtil {
}
}
}
if (filesToCompress.isEmpty()) {
if (filesToCompress.isEmpty) {
return false
}

View File

@@ -1,104 +0,0 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
open class FmtBase {
fun toUri(address: String?, port: Int?, userInfo: String?, dicQuery: HashMap<String, String>?, remark: String): String {
val query = if (dicQuery != null)
("?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) }))
else ""
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(address),
port
)
return "${url}${query}#${Utils.urlEncode(remark)}"
}
fun getStdTransport(outbound: V2rayConfig.OutboundBean, streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): HashMap<String, String> {
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = tlsSetting.spiderX.orEmpty()
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = transportDetails[2]
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = transportDetails[1]
dicQuery["key"] = transportDetails[2]
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = transportDetails[1]
dicQuery["serviceName"] = transportDetails[2]
}
}
}
return dicQuery
}
}

View File

@@ -1,102 +0,0 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.Hysteria2Bean
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object Hysteria2Fmt : FmtBase() {
fun parse(str: String): ServerConfig {
val allowInsecure = settingsStorage.decodeBool(AppConfig.PREF_ALLOW_INSECURE,false)
val config = ServerConfig.create(EConfigType.HYSTERIA2)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
if ((queryParam["insecure"].orEmpty()) == "1") true else allowInsecure,
queryParam["sni"] ?: uri.idnHost,
null,
queryParam["alpn"],
null,
null,
null
)
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
}
if (!queryParam["obfs-password"].isNullOrEmpty()) {
config.outboundBean?.settings?.obfsPassword = queryParam["obfs-password"]
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
streamSetting.tlsSettings?.let { tlsSetting ->
dicQuery["insecure"] = if (tlsSetting.allowInsecure) "1" else "0"
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
}
if (!outbound.settings?.obfsPassword.isNullOrEmpty()) {
dicQuery["obfs"] = "salamander"
dicQuery["obfs-password"] = outbound.settings?.obfsPassword ?: ""
}
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
fun toNativeConfig(config: ServerConfig, socksPort: Int): Hysteria2Bean? {
val outbound = config.getProxyOutbound() ?: return null
val tls = outbound.streamSettings?.tlsSettings
val obfs = if (outbound.settings?.obfsPassword.isNullOrEmpty()) null else
Hysteria2Bean.ObfsBean(
type = "salamander",
salamander = Hysteria2Bean.ObfsBean.SalamanderBean(
password = outbound.settings?.obfsPassword
)
)
val bean = Hysteria2Bean(
server = outbound.getServerAddressAndPort(),
auth = outbound.getPassword(),
obfs = obfs,
socks5 = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
http = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
tls = Hysteria2Bean.TlsBean(
sni = tls?.serverName ?: outbound.getServerAddress(),
insecure = tls?.allowInsecure
)
)
return bean
}
}

View File

@@ -1,153 +0,0 @@
package com.v2ray.ang.util.fmt
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
server.password = match.groupValues[2]
server.method = match.groupValues[1].lowercase()
}
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
}
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
try {
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
val method: String
val password: String
if (uri.userInfo.contains(":")) {
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
if (arrUserInfo.count() != 2) {
return false
}
method = arrUserInfo[0]
password = Utils.urlDecode(arrUserInfo[1])
} else {
val base64Decode = Utils.decode(uri.userInfo)
val arrUserInfo = base64Decode.split(":").map { it.trim() }
if (arrUserInfo.count() < 2) {
return false
}
method = arrUserInfo[0]
password = base64Decode.substringAfter(":")
}
val query = Utils.urlDecode(uri.query.orEmpty())
if (query != "") {
val queryPairs = HashMap<String, String>()
val pairs = query.split(";")
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
for (pair in pairs) {
val idx = pair.indexOf("=")
if (idx == -1) {
queryPairs[Utils.urlDecode(pair)] = ""
} else {
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
Utils.urlDecode(pair.substring(idx + 1))
}
}
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
var sni: String? = ""
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
"tcp",
"http",
queryPairs["obfs-host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
} else if (queryPairs["plugin"] == "v2ray-plugin") {
var network = "ws"
if (queryPairs["mode"] == "quic") {
network = "quic"
}
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
network,
null,
queryPairs["host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
}
if ("tls" in queryPairs) {
config.outboundBean?.streamSettings?.populateTlsSettings(
"tls", false, sni.orEmpty(), null, null, null, null, null
)
}
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = password
server.method = method
}
return true
} catch (e: Exception) {
Log.d(AppConfig.ANG_PACKAGE, e.toString())
return false
}
}
}

View File

@@ -1,64 +0,0 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
object SocksFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SOCKS)
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
result = Utils.decode(result)
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match =
legacyPattern.matchEntire(result) ?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1]
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
}
}

View File

@@ -1,90 +0,0 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object TrojanFmt : FmtBase() {
fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.TROJAN)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
var flow = ""
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
if (uri.rawQuery.isNullOrEmpty()) {
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
allowInsecure,
"",
fingerprint,
null,
null,
null,
null
)
} else {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"].orEmpty()
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: V2rayConfig.TLS,
allowInsecure,
queryParam["sni"] ?: sni.orEmpty(),
fingerprint,
queryParam["alpn"],
null,
null,
null
)
flow = queryParam["flow"].orEmpty()
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
server.flow = flow
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val dicQuery = getStdTransport(outbound, streamSetting)
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -1,80 +0,0 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object VlessFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VLESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow = queryParam["flow"].orEmpty()
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
streamSetting.populateTlsSettings(
queryParam["security"].orEmpty(),
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"].orEmpty(),
queryParam["alpn"],
queryParam["pbk"].orEmpty(),
queryParam["sid"].orEmpty(),
queryParam["spx"].orEmpty()
)
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val dicQuery = getStdTransport(outbound, streamSetting)
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["encryption"] =
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty()
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -1,155 +0,0 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object VmessFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
return parseVmessStd(str)
}
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return null
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
return null
}
val vmessQRCode = JsonUtil.fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
return null
}
config.remarks = vmessQRCode.ps
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) V2rayConfig.DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
val sni = streamSetting.populateTransportSettings(
vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path,
vmessQRCode.host
)
val fingerprint = vmessQRCode.fp
streamSetting.populateTlsSettings(
vmessQRCode.tls,
allowInsecure,
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint,
vmessQRCode.alpn,
null,
null,
null
)
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val vmessQRCode = VmessQRCode()
vmessQRCode.v = "2"
vmessQRCode.ps = config.remarks
vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString(",")).orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1]
vmessQRCode.path = transportDetails[2]
}
val json = JsonUtil.toJson(vmessQRCode)
return Utils.encode(json)
}
fun parseVmessStd(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VMESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
vnext.users[0].alterId = 0
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
streamSetting.populateTlsSettings(
queryParam["security"].orEmpty(),
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"].orEmpty(),
queryParam["alpn"],
null,
null,
null
)
return config
}
}

View File

@@ -1,92 +0,0 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.Utils
import java.net.URI
object WireguardFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery != null) {
val config = ServerConfig.create(EConfigType.WIREGUARD)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = uri.userInfo
wireguard.address =
(queryParam["address"]
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
.split(",")
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"].orEmpty()
wireguard.peers?.get(0)?.endpoint =
Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
wireguard.reserved =
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
.map { it.toInt() }
}
return config
} else {
return null
}
}
fun parseWireguardConfFile(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.WIREGUARD)
val queryParam: MutableMap<String, String> = mutableMapOf()
var currentSection: String? = null
str.lines().forEach { line ->
val trimmedLine = line.trim()
when {
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
trimmedLine.isBlank() || trimmedLine.startsWith("#") -> Unit // Skip blank lines or comments
currentSection != null -> {
val (key, value) = trimmedLine.split("=").map { it.trim() }
queryParam[key.lowercase()] = value // Store the key in lowercase for case-insensitivity
}
}
}
config.outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = queryParam["privatekey"].orEmpty()
wireguard.address = (queryParam["address"] ?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace().split(",")
wireguard.peers?.getOrNull(0)?.publicKey = queryParam["publickey"].orEmpty()
wireguard.peers?.getOrNull(0)?.endpoint = queryParam["endpoint"].orEmpty()
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
wireguard.reserved = (queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",").map { it.toInt() }
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] = outbound.settings?.peers?.get(0)?.publicKey.toString()
if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString(",")).toString()
}
dicQuery["address"] = Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString(",")).toString()
if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString()
}
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -15,34 +15,29 @@ import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ServersCache
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.util.Collections
class MainViewModel(application: Application) : AndroidViewModel(application) {
private var serverList = MmkvManager.decodeServerList()
var subscriptionId: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
var subscriptionId: String = MmkvManager.decodeSettingsString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
//var keywordFilter: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
var keywordFilter = ""
//var keywordFilter: String = MmkvManager.MmkvManager.decodeSettingsString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
var keywordFilter = ""
val serversCache = mutableListOf<ServersCache>()
val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() }
@@ -95,21 +90,19 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
&& server.contains("routing")
) {
try {
val config = ServerConfig.create(EConfigType.CUSTOM)
val config = CustomFmt.parse(server) ?: return false
config.subscriptionId = subscriptionId
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, server)
serverList.add(0, key)
val profile = ProfileItem(
configType = config.configType,
subscriptionId = config.subscriptionId,
remarks = config.remarks,
server = config.getProxyOutbound()?.getServerAddress(),
serverPort = config.getProxyOutbound()?.getServerPort(),
)
serversCache.add(0, ServersCache(key, profile))
// val profile = ProfileLiteItem(
// configType = config.configType,
// subscriptionId = config.subscriptionId,
// remarks = config.remarks,
// server = config.getProxyOutbound()?.getServerAddress(),
// serverPort = config.getProxyOutbound()?.getServerPort(),
// )
serversCache.add(0, ServersCache(key, config))
return true
} catch (e: Exception) {
e.printStackTrace()
@@ -119,7 +112,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun swapServer(fromPosition: Int, toPosition: Int) {
Collections.swap(serverList, fromPosition, toPosition)
if (subscriptionId.isEmpty()) {
Collections.swap(serverList, fromPosition, toPosition)
} else {
val fromPosition2 = serverList.indexOf(serversCache[fromPosition].guid)
val toPosition2 = serverList.indexOf(serversCache[toPosition].guid)
Collections.swap(serverList, fromPosition2, toPosition2)
}
Collections.swap(serversCache, fromPosition, toPosition)
MmkvManager.encodeServerList(serverList)
}
@@ -128,18 +127,19 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun updateCache() {
serversCache.clear()
for (guid in serverList) {
var profile = MmkvManager.decodeProfileConfig(guid)
if (profile == null) {
val config = MmkvManager.decodeServerConfig(guid) ?: continue
profile = ProfileItem(
configType = config.configType,
subscriptionId = config.subscriptionId,
remarks = config.remarks,
server = config.getProxyOutbound()?.getServerAddress(),
serverPort = config.getProxyOutbound()?.getServerPort(),
)
MmkvManager.encodeServerConfig(guid, config)
}
var profile = MmkvManager.decodeServerConfig(guid) ?: continue
// var profile = MmkvManager.decodeProfileConfig(guid)
// if (profile == null) {
// val config = MmkvManager.decodeServerConfig(guid) ?: continue
// profile = ProfileLiteItem(
// configType = config.configType,
// subscriptionId = config.subscriptionId,
// remarks = config.remarks,
// server = config.getProxyOutbound()?.getServerAddress(),
// serverPort = config.getProxyOutbound()?.getServerPort(),
// )
// MmkvManager.encodeServerConfig(guid, config)
// }
if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) {
continue
@@ -189,7 +189,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val serverPort = outbound.serverPort
if (serverAddress != null && serverPort != null) {
tcpingTestScope.launch {
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort.toInt())
launch(Dispatchers.Main) {
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
updateListAction.value = getPosition(item.guid)
@@ -220,7 +220,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun subscriptionIdChanged(id: String) {
if (subscriptionId != id) {
subscriptionId = id
MmkvManager.settingsStorage.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
MmkvManager.encodeSettings(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
reloadServerList()
}
}
@@ -252,7 +252,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun removeDuplicateServer(): Int {
val serversCacheCopy = mutableListOf<Pair<String, ServerConfig>>()
val serversCacheCopy = mutableListOf<Pair<String, ProfileItem>>()
for (it in serversCache) {
val config = MmkvManager.decodeServerConfig(it.guid) ?: continue
serversCacheCopy.add(Pair(it.guid, config))
@@ -260,11 +260,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val deleteServer = mutableListOf<String>()
serversCacheCopy.forEachIndexed { index, it ->
val outbound = it.second.getProxyOutbound()
val outbound = it.second.getKeyProperty()
serversCacheCopy.forEachIndexed { index2, it2 ->
if (index2 > index) {
val outbound2 = it2.second.getProxyOutbound()
if (outbound == outbound2 && !deleteServer.contains(it2.first)) {
val outbound2 = it2.second.getKeyProperty()
if (outbound.equals(outbound2) && !deleteServer.contains(it2.first)) {
deleteServer.add(it2.first)
}
}
@@ -318,30 +318,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MmkvManager.encodeServerList(serverList)
}
fun copyAssets(assets: AssetManager) {
val extFolder = Utils.userAssetPath(getApplication<AngApplication>())
fun initAssets(assets: AssetManager) {
viewModelScope.launch(Dispatchers.Default) {
try {
val geo = arrayOf("geosite.dat", "geoip.dat")
assets.list("")
?.filter { geo.contains(it) }
?.filter { !File(extFolder, it).exists() }
?.forEach {
val target = File(extFolder, it)
assets.open(it).use { input ->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
Log.i(
ANG_PACKAGE,
"Copied from apk assets folder to ${target.absolutePath}"
)
}
} catch (e: Exception) {
Log.e(ANG_PACKAGE, "asset copy failed", e)
}
SettingsManager.initAssets(getApplication<AngApplication>(), assets)
}
}
@@ -350,7 +329,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
return
}
keywordFilter = keyword
MmkvManager.settingsStorage.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
MmkvManager.encodeSettings(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
reloadServerList()
}

View File

@@ -6,7 +6,7 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.preference.PreferenceManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.Utils
class SettingsViewModel(application: Application) : AndroidViewModel(application),
@@ -44,8 +44,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_FRAGMENT_LENGTH,
AppConfig.PREF_FRAGMENT_INTERVAL,
AppConfig.PREF_MUX_XUDP_QUIC,
-> {
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
-> {
MmkvManager.encodeSettings(key, sharedPreferences.getString(key, ""))
}
AppConfig.PREF_ROUTE_ONLY_ENABLED,
@@ -63,21 +63,21 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_FRAGMENT_ENABLED,
AppConfig.PREF_MUX_ENABLED,
-> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
-> {
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, false))
}
AppConfig.PREF_SNIFFING_ENABLED -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, true))
}
AppConfig.PREF_MUX_CONCURRENCY,
AppConfig.PREF_MUX_XUDP_CONCURRENCY -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, "8"))
MmkvManager.encodeSettings(key, sharedPreferences.getString(key, "8"))
}
// AppConfig.PREF_PER_APP_PROXY_SET -> {
// settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
// MmkvManager.encodeSettings(key, sharedPreferences.getStringSet(key, setOf()))
// }
}
if (key == AppConfig.PREF_UI_MODE_NIGHT) {

View File

@@ -13,6 +13,25 @@
<include layout="@layout/layout_address_port" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_id3" />
<EditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -41,16 +60,35 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_id3" />
android:text="@string/server_lab_port_hop" />
<EditText
android:id="@+id/et_id"
android:id="@+id/et_port_hop"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port_hop_interval" />
<EditText
android:id="@+id/et_port_hop_interval"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<include layout="@layout/layout_tls_hysteria2" />
<LinearLayout

View File

@@ -32,26 +32,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_alterId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_alterid" />
<EditText
android:id="@+id/et_alterId"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -70,21 +70,10 @@
<EditText
android:id="@+id/et_reserved1"
android:layout_width="60dp"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
android:inputType="text" />
<EditText
android:id="@+id/et_reserved2"
android:layout_width="60dp"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
<EditText
android:id="@+id/et_reserved3"
android:layout_width="60dp"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
</LinearLayout>

View File

@@ -42,6 +42,6 @@
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:paddingStart="@dimen/padding_start"/>
android:paddingStart="@dimen/padding_start" />
</LinearLayout>

View File

@@ -6,8 +6,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -6,8 +6,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -65,6 +65,27 @@
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/allowinsecures" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_stream_pinsha256" />
<EditText
android:id="@+id/et_pinsha256"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -114,6 +114,9 @@
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
<string name="toast_action_not_allowed">الإجراء غير مسموح به</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">جار التحميل</string>

View File

@@ -113,6 +113,9 @@
<string name="msg_remark_is_duplicate">মন্তব্য ইতিমধ্যে বিদ্যমান</string>
<string name="toast_action_not_allowed">অ্যাকশন অনুমোদিত নয়</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">লোড হচ্ছে</string>
@@ -309,5 +312,4 @@
</string-array>
</resources>

View File

@@ -14,8 +14,8 @@
<string name="notification_action_more">برای اطلاعات بیشتر کلیک کنید</string>
<string name="toast_services_start">شروع خدمات</string>
<string name="toast_services_stop">توقف خدمات</string>
<string name="toast_services_success">خدمات با موفقیت شروع شد</string>
<string name="toast_services_failure">شروع خدمات انجام نشد!</string>
<string name="toast_services_success">شروع خدمات با موفقیت انجام شد</string>
<string name="toast_services_failure">شروع خدمات با موفقیت انجام نشد!</string>
<!--ServerActivity-->
<string name="title_server">فایل کانفیگ</string>
@@ -23,7 +23,7 @@
<string name="menu_item_save_config">ذخیره کانفیگ</string>
<string name="menu_item_del_config">حذف کانفیگ</string>
<string name="menu_item_import_config_qrcode">کانفیگ را از QRcode وارد کنید</string>
<string name="menu_item_import_config_clipboard">کانفیگ را از کلیپ‌بورد وارد کنید</string>
<string name="menu_item_import_config_clipboard">کانفیگ را از کلیپ ‌بورد وارد کنید</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>
@@ -33,12 +33,12 @@
<string name="menu_item_import_config_manually_wireguard">[Wireguard]تایپ دستی</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">کانفیگ سفارشی</string>
<string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشی را از کلیپ‌بورد وارد کنید</string>
<string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشی را از کلیپ ‌بورد وارد کنید</string>
<string name="menu_item_import_config_custom_local">کانفیگ سفارشی را به صورت محلی وارد کنید</string>
<string name="menu_item_import_config_custom_url">کانفیگ سفارشی را از طریق نشانی اینترنتی وارد کنید</string>
<string name="menu_item_import_config_custom_url_scan">نشانی اینترنتی اسکن کانفیگ سفارشی را وارد کنید</string>
<string name="del_config_comfirm">حذف شود؟</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string>
<string name="del_invalid_config_comfirm">لطفا قبل از حذف کانفیگ نامعتبر تایید کنید! حذف کانفیگ را تایید می کنید؟</string>
<string name="server_lab_remarks">ملاحظات</string>
<string name="server_lab_address">نشانی</string>
<string name="server_lab_port">پورت</string>
@@ -75,7 +75,7 @@
<string name="server_lab_id3">رمز عبور</string>
<string name="server_lab_security3">امنیت</string>
<string name="server_lab_id4">رمز عبور (اختیاری)</string>
<string name="server_lab_security4">نام‌کاربری (اختیاری)</string>
<string name="server_lab_security4">نام‌ کاربری (اختیاری)</string>
<string name="server_lab_encryption">رمزنگاری</string>
<string name="server_lab_flow">جریان</string>
<string name="server_lab_public_key">PublicKey</string>
@@ -87,7 +87,7 @@
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
<string name="toast_success">با موفقیت انجام شد</string>
<string name="toast_failure">شکست</string>
<string name="toast_none_data">چیزی نیست</string>
<string name="toast_none_data">هیچ داده ای وجود ندارد</string>
<string name="toast_incorrect_protocol">پروتکل نادرست</string>
<string name="toast_decoding_failed">رمزگشایی انجام نشد</string>
<string name="title_file_chooser">انتخاب فایل کانفیگ</string>
@@ -95,18 +95,21 @@
<string name="server_customize_config">کانفیگ سفارشی</string>
<string name="toast_config_file_invalid">کانفیگ معتبر نیست</string>
<string name="server_lab_content">محتوا</string>
<string name="toast_none_data_clipboard">هیچ داده‌ای در کلیپ‌بورد وجود ندارد</string>
<string name="toast_none_data_clipboard">هیچ داده‌ای در کلیپ ‌بورد وجود ندارد</string>
<string name="toast_invalid_url">نشانی اینترنتی معتبر نیست</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="toast_insecure_url_protocol">لطفاً از آدرس اشتراک پروتکل HTTP ناامن استفاده نکنید</string>
<string name="server_lab_need_inbound">اطمینان حاصل کنید که پورت ورودی با تنظیمات مطابقت دارد</string>
<string name="toast_malformed_josn">کانفیگ درست نیست</string>
<string name="server_lab_request_host6">میزبان (SNI) (اختیاری)</string>
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
<string name="menu_item_add_file">افزودن فایل‌ها</string>
<string name="menu_item_add_file">افزودن فایل ها</string>
<string name="title_url">URL</string>
<string name="menu_item_download_file">دانلود فایل‌ها</string>
<string name="menu_item_download_file">دانلود فایل‌ ها</string>
<string name="toast_action_not_allowed">این عمل ممنوع است</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_obfs_password">رمز عبور Obfs</string>
<string name="server_lab_port_hop">پورت پرش (درگاه سرور را بازنویسی می کند)</string>
<string name="server_lab_port_hop_interval">فاصله پورت پرش (ثانیه)</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">URL را اضافه کنید</string>
@@ -115,12 +118,12 @@
<string name="msg_dialog_progress">بارگذاری</string>
<string name="menu_item_search">جستجو</string>
<string name="menu_item_select_all">انتخاب همه</string>
<string name="msg_enter_keywords">کلیدواژه‌ها را وارد کنید</string>
<string name="msg_enter_keywords">کلیدواژه‌ ها را وارد کنید</string>
<string name="switch_bypass_apps_mode">حالت Bypass</string>
<string name="menu_item_select_proxy_app">انتخاب خودکار پروکسی برنامه</string>
<string name="msg_downloading_content">در حال دانلود محتوا</string>
<string name="menu_item_export_proxy_app">خروجی گرفتن در کلیپ‌بورد</string>
<string name="menu_item_import_proxy_app">وارد کردن از کلیپ‌بورد</string>
<string name="menu_item_export_proxy_app">خروجی گرفتن در کلیپ‌ بورد</string>
<string name="menu_item_import_proxy_app">وارد کردن از کلیپ‌ بورد</string>
<!-- Preferences -->
<string name="title_settings">تنظیمات</string>
@@ -147,16 +150,16 @@
<string name="summary_pref_speed_enabled">نمایش سرعت فعلی در قسمت آگاه‌سازی. \nآیکون آگاه‌سازی بر اساس استفاده تغییر می‌کند.</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="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 محلی آدرس های آیپی فیک را برمیگرداند(سریعتر می باشد اما ممکن است برای برخی از برنامه ها کار نکند)</string>
<string name="summary_pref_fake_dns_enabled">DNS محلی آدرس های آیپی فیک را بر می گرداند (سریع تر می باشد اما ممکن است برای برخی از برنامه ها کار نکند)</string>
<string name="title_pref_prefer_ipv6">ترجیح دادن IPv6</string>
<string name="summary_pref_prefer_ipv6">ترجیح دادن نشانی و مسیر های IPv6</string>
@@ -173,11 +176,11 @@
<string name="summary_pref_delay_test_url">Url</string>
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق LAN</string>
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند.</string>
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌ های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند.</string>
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</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">پورت پروکسی SOCKS5</string>
<string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string>
@@ -195,7 +198,7 @@
<string name="summary_pref_start_scan_immediate">دوربین را برای اسکن بلافاصله در هنگام راه اندازی باز کنید، در غیر این صورت می توانید کد را اسکن کنید یا عکسی را در نوار ابزار انتخاب کنید.</string>
<string name="title_pref_feedback">بازخورد</string>
<string name="summary_pref_feedback">بازخورد یا گزارش اشکالات در گیت‌هاب</string>
<string name="summary_pref_feedback">بازخورد یا گزارش اشکالات در گیت‌ هاب</string>
<string name="summary_pref_tg_group">عضویت در گروه تلگرام</string>
<string name="toast_tg_app_not_found">برنامه تلگرام پیدا نشد</string>
@@ -217,8 +220,8 @@
<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">UI mode settings</string>
<string name="title_ui_settings">تنظیمات رابط کاربری</string>
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
<string name="title_logcat">گزارشات</string>
<string name="logcat_copy">کپی</string>
@@ -227,7 +230,7 @@
<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>
@@ -240,8 +243,8 @@
<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_user_asset_setting">فایل‌های دارایی جغرافیا</string>
<string name="title_sort_by_test_results">مرتب‌سازی بر اساس نتایج آزمایش</string>
<string name="title_user_asset_setting">فایل ‌های دارایی جغرافیا</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>
@@ -251,7 +254,7 @@
<string name="routing_settings_domain_strategy">استراتژی دامنه</string>
<string name="routing_settings_title">تنظیمات مسیریابی</string>
<string name="routing_settings_tips">با کاما (,) از هم جدا شوند، ذخیره کردن فراموش نشود</string>
<string name="routing_settings_tips">با کاما (،) از هم جدا شوند، ذخیره کردن فراموش نشود</string>
<string name="routing_settings_save">ذخیره</string>
<string name="routing_settings_delete">حذف</string>
<string name="routing_settings_rule_title">تنظیمات قانون مسیریابی</string>
@@ -280,13 +283,13 @@
<string name="title_pref_fragment_enabled">فعال کردن Fragment</string>
<string-array name="share_method">
<item>QRcode</item>
<item>خروجی گرفتن در کلیپ‌بورد</item>
<item>خروجی گرفتن کانفیگ کامل در کلیپبورد</item>
<item>خروجی گرفتن در کلیپ‌ بورد</item>
<item>خروجی گرفتن کانفیگ کامل در کلیپ بورد</item>
</string-array>
<string-array name="share_sub_method">
<item>QRcode</item>
<item>خروجی گرفتن در کلیپ‌بورد</item>
<item>خروجی گرفتن در کلیپ‌ بورد</item>
</string-array>
<string-array name="mode_entries">
@@ -297,9 +300,9 @@
<string name="menu_item_add_url">افزودن لینک</string>
<string-array name="ui_mode_night">
<item>Follow system</item>
<item>Light</item>
<item>Dark</item>
<item>پیش فرض سیستم</item>
<item>روشن</item>
<item>تاریک</item>
</string-array>
<string-array name="preset_rulesets">
@@ -308,5 +311,5 @@
<item>جهانی(Global)</item>
<item>ایران</item>
</string-array>
</resources>

View File

@@ -97,7 +97,7 @@
<string name="server_lab_content">Данные</string>
<string name="toast_none_data_clipboard">В буфере обмена нет данных</string>
<string name="toast_invalid_url">Неправильный URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="toast_insecure_url_protocol">Не используйте небезопасный HTTP-протокол в адресе подписки</string>
<string name="server_lab_need_inbound">Убедитесь, что входящий порт соответствует настройкам</string>
<string name="toast_malformed_josn">Профиль повреждён</string>
<string name="server_lab_request_host6">Узел (SNI) (необязательно)</string>
@@ -112,6 +112,9 @@
<string name="msg_remark_is_duplicate">Название уже существует</string>
<string name="toast_action_not_allowed">Это действие запрещено</string>
<string name="server_obfs_password">Пароль obfs</string>
<string name="server_lab_port_hop">Переключение портов</string>
<string name="server_lab_port_hop_interval">Интервал переключения портов</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Загрузка…</string>
@@ -316,6 +319,7 @@
<item>Белый список Китая</item>
<item>Чёрный список Китая</item>
<item>Общие</item>
<item>Белый список Ирана</item>
</string-array>
</resources>

View File

@@ -107,6 +107,9 @@
<string name="menu_item_download_file">Tải xuống tệp tin</string>
<string name="toast_action_not_allowed">Hành động này bị cấm!</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">Thêm URL nội dung</string>

View File

@@ -82,7 +82,7 @@
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved(可选)</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="toast_success">成功</string>
@@ -107,6 +107,9 @@
<string name="menu_item_download_file">下载文件</string>
<string name="toast_action_not_allowed">禁止此项操作</string>
<string name="server_obfs_password">混淆密码</string>
<string name="server_lab_port_hop">跳跃端口(会覆盖服务器端口)</string>
<string name="server_lab_port_hop_interval">端口跳跃间隔(秒)</string>
<string name="server_lab_stream_pinsha256">SHA256证书指纹</string>
<!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">添加资产网址</string>

View File

@@ -82,7 +82,7 @@
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (可選)</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="toast_success">成功</string>
@@ -107,6 +107,9 @@
<string name="menu_item_download_file">下載檔案</string>
<string name="toast_action_not_allowed">禁止此項操作</string>
<string name="server_obfs_password">混淆密碼</string>
<string name="server_lab_port_hop">跳躍連接埠(會覆蓋伺服器連接埠)</string>
<string name="server_lab_port_hop_interval">連接埠跳躍間隔(秒)</string>
<string name="server_lab_stream_pinsha256">SHA256憑證指紋</string>
<!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">新增資產網址</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TabLayoutTextStyle" parent="TextAppearance.Design.Tab">
<item name="textAllCaps">false</item>
</style>

View File

@@ -83,7 +83,7 @@
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved(Optional)</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>
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
<string name="toast_success">Success</string>
@@ -113,6 +113,9 @@
<string name="msg_remark_is_duplicate">The remarks already exists</string>
<string name="toast_action_not_allowed">Action not allowed</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping(will override the port)</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Loading</string>

View File

@@ -37,5 +37,102 @@ class UtilTest {
assertTrue(Utils.isIpAddress("240e:1234:abcd:12::6666"))
assertTrue(Utils.isIpAddress("240e:1234:abcd:12::/64"))
}
// @Test
// fun test_fmtHysteria2Parse() {
// val url2 = "hysteria2://password2@127.0.0.1:443?obfs=salamander&obfs-password=obfs2&insecure=0#Hy22"
// var result2 = Hysteria2Fmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// assertTrue(result2?.obfsPassword == "obfs2")
// assertTrue(result2?.security == "tls")
//
// var url22 = Hysteria2Fmt.toUri(result2!!)
// assertTrue(url22.contains("obfs2"))
// }
//
// @Test
// fun test_fmtSsParse() {
// val url2 = "ss://aa:bb@127.0.0.1:10000#sss"
// var result2 = ShadowsocksFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
//
// var result = ShadowsocksFmt.parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQy@127.0.0.1:10000#sss")
// assertTrue(result != null)
// assertTrue(result?.server == "127.0.0.1")
// }
//
// @Test
// fun test_fmtSocksParse() {
// val url2 = "socks://Og%3D%3D@127.0.0.1:1000#socks2"
// var result2 = SocksFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// var url22 = SocksFmt.toUri(result2!!)
// assertTrue(url2.contains(url22))
//
// var result = SocksFmt.parse("socks://dXNlcjpwYXNz@127.0.0.1:1000#socks2")
// assertTrue(result != null)
// assertTrue(result?.server == "127.0.0.1")
// }
//
// @Test
// fun test_fmtTrojanParse() {
// val url2 = "trojan://password2@127.0.0.1:443?flow=xtls-rprx-vision&security=tls&type=tcp&headerType=none#Trojan"
// var result2 = TrojanFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// assertTrue(result2?.flow == "xtls-rprx-vision")
//
// val url = "trojan://password2@127.0.0.1:443#Trojan"
// var result = TrojanFmt.parse(url)
// assertTrue(result != null)
// assertTrue(result?.server == "127.0.0.1")
// assertTrue(result?.security == "tls")
//
//
// }
//
// @Test
// fun test_fmtVlessParse() {
// val url2 =
// "vless://cae1dc39-0547-4b1d-9e7a-01132c7ae3a7@127.0.0.1:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=sni2&fp=chrome&pbk=publickkey&sid=123456&spx=%2F&type=ws&host=host2&path=path2#VLESS"
// var result2 = VlessFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// assertTrue(result2?.flow == "xtls-rprx-vision")
//
//
// var url22 = VlessFmt.toUri(result2!!)
// assertTrue(url22.contains("xtls-rprx-vision"))
//
// }
//
// @Test
// fun test_fmtVmessParse() {
// val url2 =
// "vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIlZtZXNzIiwNCiAgImFkZCI6ICIxMjcuMC4wLjEiLA0KICAicG9ydCI6ICIxMDAwMCIsDQogICJpZCI6ICJlYmI5MWM5OS1lZjA3LTRmZjUtOThhYS01OTAyYWI0ZDAyODYiLA0KICAiYWlkIjogIjEyMyIsDQogICJzY3kiOiAiYWVzLTEyOC1nY20iLA0KICAibmV0IjogInRjcCIsDQogICJ0eXBlIjogIm5vbmUiLA0KICAiaG9zdCI6ICJob3N0MiIsDQogICJwYXRoIjogInBhdGgyIiwNCiAgInRscyI6ICIiLA0KICAic25pIjogIiIsDQogICJhbHBuIjogIiINCn0="
// var result2 = VmessFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// assertTrue(result2?.method == "aes-128-gcm")
//
// }
//
//
// @Test
// fun test_fmtWireguardParse() {
// val url2 = "wireguard://privatekey2@127.0.0.1:2000?publickey=publickey2&reserved=2%2C2%2C3&address=127.0.0.127&mtu=1250#WGG"
// var result2 = WireguardFmt.parse(url2)
// assertTrue(result2 != null)
// assertTrue(result2?.server == "127.0.0.1")
// assertTrue(result2?.publicKey == "publickey2")
// assertTrue(result2?.localAddress == "127.0.0.127")
//
//
// var url22 = WireguardFmt.toUri(result2!!)
// assertTrue(url22.contains("publickey2"))
// }
}

View File

@@ -27,6 +27,7 @@ viewpager2 = "1.1.0"
workRuntimeKtx = "2.9.1"
androidGradlePlugin = "8.7.1"
androidKotlinPlugin = "2.0.21"
mockitoMockitoInline = "4.0.0"
[libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
@@ -61,6 +62,8 @@ toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastc
viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workRuntimeKtx" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoMockitoInline" }
org-mockito-mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoMockitoInline" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }