Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f252d1395a | |||
| 2dc0472c69 | |||
| 3e09adc4d1 | |||
| 11750b9382 | |||
| 30a4c2199a | |||
| 406a9f996e | |||
| 5373579bd5 | |||
| 9b4cc201e7 | |||
| 6f2c96c2b6 | |||
| 1f6104de8b | |||
| e078a2ab27 | |||
| 5695c17908 | |||
| e2c1081d5a | |||
| bcbcbc91c7 | |||
| 5eb3566e8d | |||
| d75eca8dd4 | |||
| 640c16d8dc | |||
| 1ba5c5a7a6 | |||
| f67af69dda | |||
| 5d47777307 | |||
| ab22bb9804 | |||
| 2626462e49 | |||
| 41893d79c0 | |||
| 834c1ba63d | |||
| 633ee63891 | |||
| eb5627c0d0 | |||
| 6db38f6e3d | |||
| c69a758429 | |||
| cee3a0ffec | |||
| 18c0143186 | |||
| bbf0b05b49 | |||
| 44723c56ad | |||
| e53c36b53b | |||
| 80f26cd4b8 | |||
| b023414cd0 | |||
| 2f56104565 | |||
| 9cd5fefdca | |||
| 25c42c475f | |||
| 96e66da071 | |||
| 6914b9ee1b | |||
| cfc6546c97 | |||
| 0880313659 | |||
| 875ca02126 | |||
| c2d5925053 | |||
| 547bbf8e95 | |||
| 2218251b03 | |||
| 4da3a23162 | |||
| 28a90baf88 | |||
| 7bbdda2f2f | |||
| 884b444a41 | |||
| e1ff2df36e | |||
| 61c0111778 | |||
| bfc9a64e07 | |||
| 2ba92045cc | |||
| aeca9f51c8 | |||
| b107c0ac1d | |||
| 65a04b4784 | |||
| eab9f50cfd | |||
| b8bb83b524 | |||
| 93eb9fe3b9 | |||
| 5f167512f5 | |||
| a727b81263 | |||
| f27c9192d1 | |||
| 90153fa17f | |||
| cdfaa01852 | |||
| 33d2c3b00d | |||
| b7f992cdc0 | |||
| 0a6a24e309 | |||
| 153b4cffef | |||
| f09a413232 | |||
| cba58f6ae2 | |||
| 2bf4b91488 | |||
| b60b7f4307 | |||
| e4ca04a096 | |||
| d0f7ecec44 |
@@ -1,19 +1,20 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
alias(libs.plugins.android.application)
|
||||||
id("org.jetbrains.kotlin.android")
|
alias(libs.plugins.kotlin.android)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.v2ray.ang"
|
namespace = "com.v2ray.ang"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 604
|
versionCode = 615
|
||||||
versionName = "1.9.10"
|
versionName = "1.9.19"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
@@ -27,20 +28,16 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
}
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
debug {
|
"proguard-rules.pro"
|
||||||
isMinifyEnabled = false
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +47,13 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
@@ -69,7 +71,8 @@ android {
|
|||||||
|
|
||||||
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
||||||
if (versionCodes.containsKey(abi)) {
|
if (versionCodes.containsKey(abi)) {
|
||||||
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
output.versionCodeOverride =
|
||||||
|
(1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
||||||
} else {
|
} else {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
@@ -86,49 +89,60 @@ android {
|
|||||||
useLegacyPackaging = true
|
useLegacyPackaging = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Core Libraries
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
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 Core Libraries
|
||||||
// Androidx
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.constraintlayout)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.legacy.support.v4)
|
implementation(libs.androidx.activity)
|
||||||
implementation(libs.appcompat)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.material)
|
|
||||||
implementation(libs.cardview)
|
|
||||||
implementation(libs.preference.ktx)
|
implementation(libs.preference.ktx)
|
||||||
implementation(libs.recyclerview)
|
implementation(libs.recyclerview)
|
||||||
implementation(libs.fragment.ktx)
|
|
||||||
implementation(libs.multidex)
|
|
||||||
implementation(libs.viewpager2)
|
|
||||||
|
|
||||||
// Androidx ktx
|
// UI Libraries
|
||||||
implementation(libs.activity.ktx)
|
implementation(libs.material)
|
||||||
|
implementation(libs.toastcompat)
|
||||||
|
implementation(libs.editorkit)
|
||||||
|
implementation(libs.flexbox)
|
||||||
|
|
||||||
|
// Data and Storage Libraries
|
||||||
|
implementation(libs.mmkv.static)
|
||||||
|
implementation(libs.gson)
|
||||||
|
|
||||||
|
// Reactive and Utility Libraries
|
||||||
|
implementation(libs.rxjava)
|
||||||
|
implementation(libs.rxandroid)
|
||||||
|
implementation(libs.rxpermissions)
|
||||||
|
|
||||||
|
// Language and Processing Libraries
|
||||||
|
implementation(libs.language.base)
|
||||||
|
implementation(libs.language.json)
|
||||||
|
|
||||||
|
// Intent and Utility Libraries
|
||||||
|
implementation(libs.quickie.bundled)
|
||||||
|
implementation(libs.core)
|
||||||
|
|
||||||
|
// AndroidX Lifecycle and Architecture Components
|
||||||
implementation(libs.lifecycle.viewmodel.ktx)
|
implementation(libs.lifecycle.viewmodel.ktx)
|
||||||
implementation(libs.lifecycle.livedata.ktx)
|
implementation(libs.lifecycle.livedata.ktx)
|
||||||
implementation(libs.lifecycle.runtime.ktx)
|
implementation(libs.lifecycle.runtime.ktx)
|
||||||
|
|
||||||
//kotlin
|
// Background Task Libraries
|
||||||
implementation(libs.kotlin.reflect)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
|
||||||
|
|
||||||
implementation(libs.mmkv.static)
|
|
||||||
implementation(libs.gson)
|
|
||||||
implementation(libs.rxjava)
|
|
||||||
implementation(libs.rxandroid)
|
|
||||||
implementation(libs.rxpermissions)
|
|
||||||
implementation(libs.toastcompat)
|
|
||||||
implementation(libs.editorkit)
|
|
||||||
implementation(libs.language.base)
|
|
||||||
implementation(libs.language.json)
|
|
||||||
implementation(libs.quickie.bundled)
|
|
||||||
implementation(libs.core)
|
|
||||||
implementation(libs.work.runtime.ktx)
|
implementation(libs.work.runtime.ktx)
|
||||||
implementation(libs.work.multiprocess)
|
implementation(libs.work.multiprocess)
|
||||||
}
|
|
||||||
|
// Multidex Support
|
||||||
|
implementation(libs.multidex)
|
||||||
|
|
||||||
|
// Testing Libraries
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
testImplementation(libs.org.mockito.mockito.inline)
|
||||||
|
testImplementation(libs.mockito.kotlin)
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -41,14 +41,90 @@
|
|||||||
"geosite:private"
|
"geosite:private"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"remarks": "代理海外公共DNSIP",
|
||||||
|
"outboundTag": "proxy",
|
||||||
|
"ip": [
|
||||||
|
"1.1.1.1",
|
||||||
|
"1.0.0.1",
|
||||||
|
"2606:4700:4700::1111",
|
||||||
|
"2606:4700:4700::1001",
|
||||||
|
"1.1.1.2",
|
||||||
|
"1.0.0.2",
|
||||||
|
"2606:4700:4700::1112",
|
||||||
|
"2606:4700:4700::1002",
|
||||||
|
"1.1.1.3",
|
||||||
|
"1.0.0.3",
|
||||||
|
"2606:4700:4700::1113",
|
||||||
|
"2606:4700:4700::1003",
|
||||||
|
"8.8.8.8",
|
||||||
|
"8.8.4.4",
|
||||||
|
"2001:4860:4860::8888",
|
||||||
|
"2001:4860:4860::8844",
|
||||||
|
"94.140.14.14",
|
||||||
|
"94.140.15.15",
|
||||||
|
"2a10:50c0::ad1:ff",
|
||||||
|
"2a10:50c0::ad2:ff",
|
||||||
|
"94.140.14.15",
|
||||||
|
"94.140.15.16",
|
||||||
|
"2a10:50c0::bad1:ff",
|
||||||
|
"2a10:50c0::bad2:ff",
|
||||||
|
"94.140.14.140",
|
||||||
|
"94.140.14.141",
|
||||||
|
"2a10:50c0::1:ff",
|
||||||
|
"2a10:50c0::2:ff",
|
||||||
|
"208.67.222.222",
|
||||||
|
"208.67.220.220",
|
||||||
|
"2620:119:35::35",
|
||||||
|
"2620:119:53::53",
|
||||||
|
"208.67.222.123",
|
||||||
|
"208.67.220.123",
|
||||||
|
"2620:119:35::123",
|
||||||
|
"2620:119:53::123",
|
||||||
|
"9.9.9.9",
|
||||||
|
"149.112.112.112",
|
||||||
|
"2620:fe::9",
|
||||||
|
"2620:fe::fe",
|
||||||
|
"9.9.9.11",
|
||||||
|
"149.112.112.11",
|
||||||
|
"2620:fe::11",
|
||||||
|
"2620:fe::fe:11",
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10",
|
||||||
|
"77.88.8.8",
|
||||||
|
"77.88.8.1",
|
||||||
|
"2a02:6b8::feed:0ff",
|
||||||
|
"2a02:6b8:0:1::feed:0ff",
|
||||||
|
"77.88.8.88",
|
||||||
|
"77.88.8.2",
|
||||||
|
"2a02:6b8::feed:bad",
|
||||||
|
"2a02:6b8:0:1::feed:bad",
|
||||||
|
"77.88.8.7",
|
||||||
|
"77.88.8.3",
|
||||||
|
"2a02:6b8::feed:a11",
|
||||||
|
"2a02:6b8:0:1::feed:a11"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remarks": "代理海外公共DNS域名",
|
||||||
|
"outboundTag": "proxy",
|
||||||
|
"domain": [
|
||||||
|
"domain:cloudflare-dns.com",
|
||||||
|
"domain:one.one.one.one",
|
||||||
|
"domain:dns.google",
|
||||||
|
"domain:adguard-dns.com",
|
||||||
|
"domain:opendns.com",
|
||||||
|
"domain:umbrella.com",
|
||||||
|
"domain:quad9.net",
|
||||||
|
"domain:yandex.net"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"remarks": "代理IP",
|
"remarks": "代理IP",
|
||||||
"outboundTag": "proxy",
|
"outboundTag": "proxy",
|
||||||
"ip": [
|
"ip": [
|
||||||
"1.0.0.1",
|
|
||||||
"1.1.1.1",
|
|
||||||
"8.8.8.8",
|
|
||||||
"8.8.4.4",
|
|
||||||
"geoip:facebook",
|
"geoip:facebook",
|
||||||
"geoip:fastly",
|
"geoip:fastly",
|
||||||
"geoip:google",
|
"geoip:google",
|
||||||
|
|||||||
@@ -34,29 +34,62 @@
|
|||||||
"geosite:private"
|
"geosite:private"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"remarks": "绕过中国公共DNSIP",
|
||||||
|
"outboundTag": "direct",
|
||||||
|
"ip": [
|
||||||
|
"223.5.5.5",
|
||||||
|
"223.6.6.6",
|
||||||
|
"2400:3200::1",
|
||||||
|
"2400:3200:baba::1",
|
||||||
|
"119.29.29.29",
|
||||||
|
"1.12.12.12",
|
||||||
|
"120.53.53.53",
|
||||||
|
"2402:4e00::",
|
||||||
|
"2402:4e00:1::",
|
||||||
|
"180.76.76.76",
|
||||||
|
"2400:da00::6666",
|
||||||
|
"114.114.114.114",
|
||||||
|
"114.114.115.115",
|
||||||
|
"114.114.114.119",
|
||||||
|
"114.114.115.119",
|
||||||
|
"114.114.114.110",
|
||||||
|
"114.114.115.110",
|
||||||
|
"180.184.1.1",
|
||||||
|
"180.184.2.2",
|
||||||
|
"101.226.4.6",
|
||||||
|
"218.30.118.6",
|
||||||
|
"123.125.81.6",
|
||||||
|
"140.207.198.6",
|
||||||
|
"1.2.4.8",
|
||||||
|
"210.2.4.8",
|
||||||
|
"52.80.66.66",
|
||||||
|
"117.50.22.22",
|
||||||
|
"2400:7fc0:849e:200::4",
|
||||||
|
"2404:c2c0:85d8:901::4",
|
||||||
|
"117.50.10.10",
|
||||||
|
"52.80.52.52",
|
||||||
|
"2400:7fc0:849e:200::8",
|
||||||
|
"2404:c2c0:85d8:901::8",
|
||||||
|
"117.50.60.30",
|
||||||
|
"52.80.60.30"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remarks": "绕过中国公共DNS域名",
|
||||||
|
"outboundTag": "direct",
|
||||||
|
"domain": [
|
||||||
|
"domain:alidns.com",
|
||||||
|
"domain:doh.pub",
|
||||||
|
"domain:dot.pub",
|
||||||
|
"domain:360.cn",
|
||||||
|
"domain:onedns.net"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"remarks": "绕过中国IP",
|
"remarks": "绕过中国IP",
|
||||||
"outboundTag": "direct",
|
"outboundTag": "direct",
|
||||||
"ip": [
|
"ip": [
|
||||||
"223.5.5.5/32",
|
|
||||||
"223.6.6.6/32",
|
|
||||||
"2400:3200::1/128",
|
|
||||||
"2400:3200:baba::1/128",
|
|
||||||
"119.29.29.29/32",
|
|
||||||
"1.12.12.12/32",
|
|
||||||
"120.53.53.53/32",
|
|
||||||
"2402:4e00::/128",
|
|
||||||
"2402:4e00:1::/128",
|
|
||||||
"180.76.76.76/32",
|
|
||||||
"2400:da00::6666/128",
|
|
||||||
"114.114.114.114/32",
|
|
||||||
"114.114.115.115/32",
|
|
||||||
"180.184.1.1/32",
|
|
||||||
"180.184.2.2/32",
|
|
||||||
"101.226.4.6/32",
|
|
||||||
"218.30.118.6/32",
|
|
||||||
"123.125.81.6/32",
|
|
||||||
"140.207.198.6/32",
|
|
||||||
"geoip:cn"
|
"geoip:cn"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -64,13 +97,7 @@
|
|||||||
"remarks": "绕过中国域名",
|
"remarks": "绕过中国域名",
|
||||||
"outboundTag": "direct",
|
"outboundTag": "direct",
|
||||||
"domain": [
|
"domain": [
|
||||||
"domain:dns.alidns.com",
|
"geosite:cn"
|
||||||
"domain:doh.pub",
|
|
||||||
"domain:dot.pub",
|
|
||||||
"domain:doh.360.cn",
|
|
||||||
"domain:dot.360.cn",
|
|
||||||
"geosite:cn",
|
|
||||||
"geosite:geolocation-cn"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-2
@@ -7,6 +7,7 @@ import androidx.multidex.MultiDexApplication
|
|||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.handler.SettingsManager
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class AngApplication : MultiDexApplication() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val workManagerConfiguration: Configuration = Configuration.Builder()
|
private val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||||
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
.setDefaultProcessName("${ANG_PACKAGE}:bg")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -37,7 +38,7 @@ class AngApplication : MultiDexApplication() {
|
|||||||
|
|
||||||
MMKV.initialize(this)
|
MMKV.initialize(this)
|
||||||
|
|
||||||
Utils.setNightMode(application)
|
Utils.setNightMode()
|
||||||
// Initialize WorkManager with the custom configuration
|
// Initialize WorkManager with the custom configuration
|
||||||
WorkManager.initialize(this, workManagerConfiguration)
|
WorkManager.initialize(this, workManagerConfiguration)
|
||||||
|
|
||||||
+9
-4
@@ -161,15 +161,20 @@ object AppConfig {
|
|||||||
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
||||||
|
|
||||||
// Android Private DNS constants
|
// Android Private DNS constants
|
||||||
const val DNS_PUB_DOMAIN = "dns.pub"
|
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
||||||
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
||||||
const val DNS_ONE_ONE_DOMAIN = "one.one.one.one"
|
const val DNS_CLOUDFLARE_DOMAIN = "one.one.one.one"
|
||||||
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
||||||
|
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
||||||
|
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||||
|
|
||||||
val DNS_PUB_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
|
||||||
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||||
val DNS_ONE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||||
|
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||||
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||||
|
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
||||||
|
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
||||||
|
|
||||||
const val DEFAULT_PORT = 443
|
const val DEFAULT_PORT = 443
|
||||||
const val DEFAULT_SECURITY = "auto"
|
const val DEFAULT_SECURITY = "auto"
|
||||||
+2
-1
@@ -8,7 +8,8 @@ enum class Language(val code: String) {
|
|||||||
VIETNAMESE("vi"),
|
VIETNAMESE("vi"),
|
||||||
RUSSIAN("ru"),
|
RUSSIAN("ru"),
|
||||||
PERSIAN("fa"),
|
PERSIAN("fa"),
|
||||||
BANGLA("bn");
|
BANGLA("bn"),
|
||||||
|
BAKHTIARI("bqi-rIR");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromCode(code: String): Language {
|
fun fromCode(code: String): Language {
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
enum class NetworkType(val type: String) {
|
||||||
|
TCP("tcp"),
|
||||||
|
KCP("kcp"),
|
||||||
|
WS("ws"),
|
||||||
|
HTTP_UPGRADE("httpupgrade"),
|
||||||
|
SPLIT_HTTP("splithttp"),
|
||||||
|
XHTTP("xhttp"),
|
||||||
|
HTTP("http"),
|
||||||
|
H2("h2"),
|
||||||
|
//QUIC("quic"),
|
||||||
|
GRPC("grpc");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(type: String?) = entries.find { it.type == type } ?: TCP
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-5
@@ -30,6 +30,8 @@ data class ProfileItem(
|
|||||||
var mode: String? = null,
|
var mode: String? = null,
|
||||||
var serviceName: String? = null,
|
var serviceName: String? = null,
|
||||||
var authority: String? = null,
|
var authority: String? = null,
|
||||||
|
var xhttpMode: String? = null,
|
||||||
|
var xhttpExtra: String? = null,
|
||||||
|
|
||||||
var security: String? = null,
|
var security: String? = null,
|
||||||
var sni: String? = null,
|
var sni: String? = null,
|
||||||
@@ -42,6 +44,7 @@ data class ProfileItem(
|
|||||||
var spiderX: String? = null,
|
var spiderX: String? = null,
|
||||||
|
|
||||||
var secretKey: String? = null,
|
var secretKey: String? = null,
|
||||||
|
var preSharedKey: String? = null,
|
||||||
var localAddress: String? = null,
|
var localAddress: String? = null,
|
||||||
var reserved: String? = null,
|
var reserved: String? = null,
|
||||||
var mtu: Int? = null,
|
var mtu: Int? = null,
|
||||||
@@ -66,10 +69,45 @@ data class ProfileItem(
|
|||||||
return Utils.getIpv6Address(server) + ":" + serverPort
|
return Utils.getIpv6Address(server) + ":" + serverPort
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getKeyProperty(): ProfileItem {
|
override fun equals(other: Any?): Boolean {
|
||||||
val copy = this.copy()
|
if (other == null) return false
|
||||||
copy.subscriptionId = ""
|
val obj = other as ProfileItem
|
||||||
copy.addedTime = 0L
|
|
||||||
return copy
|
return (this.server == obj.server
|
||||||
|
&& this.serverPort == obj.serverPort
|
||||||
|
&& this.password == obj.password
|
||||||
|
&& this.method == obj.method
|
||||||
|
&& this.flow == obj.flow
|
||||||
|
&& this.username == obj.username
|
||||||
|
|
||||||
|
&& this.network == obj.network
|
||||||
|
&& this.headerType == obj.headerType
|
||||||
|
&& this.host == obj.host
|
||||||
|
&& this.path == obj.path
|
||||||
|
&& this.seed == obj.seed
|
||||||
|
&& this.quicSecurity == obj.quicSecurity
|
||||||
|
&& this.quicKey == obj.quicKey
|
||||||
|
&& this.mode == obj.mode
|
||||||
|
&& this.serviceName == obj.serviceName
|
||||||
|
&& this.authority == obj.authority
|
||||||
|
&& this.xhttpMode == obj.xhttpMode
|
||||||
|
|
||||||
|
&& this.security == obj.security
|
||||||
|
&& this.sni == obj.sni
|
||||||
|
&& this.alpn == obj.alpn
|
||||||
|
&& this.fingerPrint == obj.fingerPrint
|
||||||
|
&& this.publicKey == obj.publicKey
|
||||||
|
&& this.shortId == obj.shortId
|
||||||
|
|
||||||
|
&& this.secretKey == obj.secretKey
|
||||||
|
&& this.localAddress == obj.localAddress
|
||||||
|
&& this.reserved == obj.reserved
|
||||||
|
&& this.mtu == obj.mtu
|
||||||
|
|
||||||
|
&& this.obfsPassword == obj.obfsPassword
|
||||||
|
&& this.portHopping == obj.portHopping
|
||||||
|
&& this.portHoppingInterval == obj.portHoppingInterval
|
||||||
|
&& this.pinSHA256 == obj.pinSHA256
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+72
-58
@@ -195,6 +195,7 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
data class WireGuardBean(
|
data class WireGuardBean(
|
||||||
var publicKey: String = "",
|
var publicKey: String = "",
|
||||||
|
var preSharedKey: String = "",
|
||||||
var endpoint: String = ""
|
var endpoint: String = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -206,7 +207,7 @@ data class V2rayConfig(
|
|||||||
var kcpSettings: KcpSettingsBean? = null,
|
var kcpSettings: KcpSettingsBean? = null,
|
||||||
var wsSettings: WsSettingsBean? = null,
|
var wsSettings: WsSettingsBean? = null,
|
||||||
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
||||||
var splithttpSettings: SplithttpSettingsBean? = null,
|
var xhttpSettings: XhttpSettingsBean? = null,
|
||||||
var httpSettings: HttpSettingsBean? = null,
|
var httpSettings: HttpSettingsBean? = null,
|
||||||
var tlsSettings: TlsSettingsBean? = null,
|
var tlsSettings: TlsSettingsBean? = null,
|
||||||
var quicSettings: QuicSettingBean? = null,
|
var quicSettings: QuicSettingBean? = null,
|
||||||
@@ -275,11 +276,11 @@ data class V2rayConfig(
|
|||||||
val acceptProxyProtocol: Boolean? = null
|
val acceptProxyProtocol: Boolean? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SplithttpSettingsBean(
|
data class XhttpSettingsBean(
|
||||||
var path: String? = null,
|
var path: String? = null,
|
||||||
var host: String? = null,
|
var host: String? = null,
|
||||||
val maxUploadSize: Int? = null,
|
var mode: String? = null,
|
||||||
val maxConcurrentUploads: Int? = null
|
var extra: Any? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HttpSettingsBean(
|
data class HttpSettingsBean(
|
||||||
@@ -344,14 +345,21 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun populateTransportSettings(
|
fun populateTransportSettings(
|
||||||
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
transport: String,
|
||||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
headerType: String?,
|
||||||
|
host: String?,
|
||||||
|
path: String?,
|
||||||
|
seed: String?,
|
||||||
|
quicSecurity: String?,
|
||||||
|
key: String?,
|
||||||
|
mode: String?,
|
||||||
|
serviceName: String?,
|
||||||
authority: String?
|
authority: String?
|
||||||
): String? {
|
): String? {
|
||||||
var sni: String? = null
|
var sni: String? = null
|
||||||
network = transport
|
network = transport
|
||||||
when (network) {
|
when (network) {
|
||||||
"tcp" -> {
|
NetworkType.TCP.type -> {
|
||||||
val tcpSetting = TcpSettingsBean()
|
val tcpSetting = TcpSettingsBean()
|
||||||
if (headerType == AppConfig.HEADER_TYPE_HTTP) {
|
if (headerType == AppConfig.HEADER_TYPE_HTTP) {
|
||||||
tcpSetting.header.type = AppConfig.HEADER_TYPE_HTTP
|
tcpSetting.header.type = AppConfig.HEADER_TYPE_HTTP
|
||||||
@@ -369,7 +377,7 @@ data class V2rayConfig(
|
|||||||
tcpSettings = tcpSetting
|
tcpSettings = tcpSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp" -> {
|
NetworkType.KCP.type -> {
|
||||||
val kcpsetting = KcpSettingsBean()
|
val kcpsetting = KcpSettingsBean()
|
||||||
kcpsetting.header.type = headerType ?: "none"
|
kcpsetting.header.type = headerType ?: "none"
|
||||||
if (seed.isNullOrEmpty()) {
|
if (seed.isNullOrEmpty()) {
|
||||||
@@ -380,7 +388,7 @@ data class V2rayConfig(
|
|||||||
kcpSettings = kcpsetting
|
kcpSettings = kcpsetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws" -> {
|
NetworkType.WS.type -> {
|
||||||
val wssetting = WsSettingsBean()
|
val wssetting = WsSettingsBean()
|
||||||
wssetting.headers.Host = host.orEmpty()
|
wssetting.headers.Host = host.orEmpty()
|
||||||
sni = wssetting.headers.Host
|
sni = wssetting.headers.Host
|
||||||
@@ -388,7 +396,7 @@ data class V2rayConfig(
|
|||||||
wsSettings = wssetting
|
wsSettings = wssetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"httpupgrade" -> {
|
NetworkType.HTTP_UPGRADE.type -> {
|
||||||
val httpupgradeSetting = HttpupgradeSettingsBean()
|
val httpupgradeSetting = HttpupgradeSettingsBean()
|
||||||
httpupgradeSetting.host = host.orEmpty()
|
httpupgradeSetting.host = host.orEmpty()
|
||||||
sni = httpupgradeSetting.host
|
sni = httpupgradeSetting.host
|
||||||
@@ -396,16 +404,16 @@ data class V2rayConfig(
|
|||||||
httpupgradeSettings = httpupgradeSetting
|
httpupgradeSettings = httpupgradeSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"splithttp" -> {
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
|
||||||
val splithttpSetting = SplithttpSettingsBean()
|
val xhttpSetting = XhttpSettingsBean()
|
||||||
splithttpSetting.host = host.orEmpty()
|
xhttpSetting.host = host.orEmpty()
|
||||||
sni = splithttpSetting.host
|
sni = xhttpSetting.host
|
||||||
splithttpSetting.path = path ?: "/"
|
xhttpSetting.path = path ?: "/"
|
||||||
splithttpSettings = splithttpSetting
|
xhttpSettings = xhttpSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"h2", "http" -> {
|
NetworkType.H2.type, NetworkType.HTTP.type -> {
|
||||||
network = "h2"
|
network = NetworkType.H2.type
|
||||||
val h2Setting = HttpSettingsBean()
|
val h2Setting = HttpSettingsBean()
|
||||||
h2Setting.host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
h2Setting.host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
sni = h2Setting.host.getOrNull(0) ?: sni
|
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||||
@@ -413,15 +421,15 @@ data class V2rayConfig(
|
|||||||
httpSettings = h2Setting
|
httpSettings = h2Setting
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
// "quic" -> {
|
||||||
val quicsetting = QuicSettingBean()
|
// val quicsetting = QuicSettingBean()
|
||||||
quicsetting.security = quicSecurity ?: "none"
|
// quicsetting.security = quicSecurity ?: "none"
|
||||||
quicsetting.key = key.orEmpty()
|
// quicsetting.key = key.orEmpty()
|
||||||
quicsetting.header.type = headerType ?: "none"
|
// quicsetting.header.type = headerType ?: "none"
|
||||||
quicSettings = quicsetting
|
// quicSettings = quicsetting
|
||||||
}
|
// }
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC.type -> {
|
||||||
val grpcSetting = GrpcSettingsBean()
|
val grpcSetting = GrpcSettingsBean()
|
||||||
grpcSetting.multiMode = mode == "multi"
|
grpcSetting.multiMode = mode == "multi"
|
||||||
grpcSetting.serviceName = serviceName.orEmpty()
|
grpcSetting.serviceName = serviceName.orEmpty()
|
||||||
@@ -436,8 +444,14 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun populateTlsSettings(
|
fun populateTlsSettings(
|
||||||
streamSecurity: String, allowInsecure: Boolean, sni: String?, fingerprint: String?, alpns: String?,
|
streamSecurity: String,
|
||||||
publicKey: String?, shortId: String?, spiderX: String?
|
allowInsecure: Boolean,
|
||||||
|
sni: String?,
|
||||||
|
fingerprint: String?,
|
||||||
|
alpns: String?,
|
||||||
|
publicKey: String?,
|
||||||
|
shortId: String?,
|
||||||
|
spiderX: String?
|
||||||
) {
|
) {
|
||||||
security = streamSecurity
|
security = streamSecurity
|
||||||
val tlsSetting = TlsSettingsBean(
|
val tlsSetting = TlsSettingsBean(
|
||||||
@@ -470,16 +484,16 @@ data class V2rayConfig(
|
|||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.vnext?.get(0)?.address
|
return settings?.vnext?.first()?.address
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.servers?.get(0)?.address
|
return settings?.servers?.first()?.address
|
||||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||||
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
|
return settings?.peers?.first()?.endpoint?.substringBeforeLast(":")
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -488,16 +502,16 @@ data class V2rayConfig(
|
|||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.vnext?.get(0)?.port
|
return settings?.vnext?.first()?.port
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.servers?.get(0)?.port
|
return settings?.servers?.first()?.port
|
||||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||||
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
|
return settings?.peers?.first()?.endpoint?.substringAfterLast(":")?.toInt()
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -512,16 +526,16 @@ data class V2rayConfig(
|
|||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
return settings?.vnext?.first()?.users?.first()?.id
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.servers?.get(0)?.password
|
return settings?.servers?.first()?.password
|
||||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||||
) {
|
) {
|
||||||
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
return settings?.servers?.first()?.users?.first()?.pass
|
||||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||||
return settings?.secretKey
|
return settings?.secretKey
|
||||||
}
|
}
|
||||||
@@ -530,9 +544,9 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
fun getSecurityEncryption(): String? {
|
fun getSecurityEncryption(): String? {
|
||||||
return when {
|
return when {
|
||||||
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.security
|
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.first()?.users?.first()?.security
|
||||||
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.encryption
|
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.first()?.users?.first()?.encryption
|
||||||
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.get(0)?.method
|
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.first()?.method
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,7 +559,7 @@ data class V2rayConfig(
|
|||||||
) {
|
) {
|
||||||
val transport = streamSettings?.network ?: return null
|
val transport = streamSettings?.network ?: return null
|
||||||
return when (transport) {
|
return when (transport) {
|
||||||
"tcp" -> {
|
NetworkType.TCP.type -> {
|
||||||
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
tcpSetting.header.type,
|
tcpSetting.header.type,
|
||||||
@@ -554,7 +568,7 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp" -> {
|
NetworkType.KCP.type -> {
|
||||||
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
kcpSetting.header.type,
|
kcpSetting.header.type,
|
||||||
@@ -563,7 +577,7 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws" -> {
|
NetworkType.WS.type -> {
|
||||||
val wsSetting = streamSettings?.wsSettings ?: return null
|
val wsSetting = streamSettings?.wsSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
"",
|
"",
|
||||||
@@ -572,7 +586,7 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"httpupgrade" -> {
|
NetworkType.HTTP_UPGRADE.type -> {
|
||||||
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
|
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
"",
|
"",
|
||||||
@@ -581,16 +595,16 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"splithttp" -> {
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
|
||||||
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
|
val xhttpSettings = streamSettings?.xhttpSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
"",
|
"",
|
||||||
splithttpSetting.host,
|
xhttpSettings.host,
|
||||||
splithttpSetting.path
|
xhttpSettings.path
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"h2" -> {
|
NetworkType.H2.type -> {
|
||||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
"",
|
"",
|
||||||
@@ -599,16 +613,16 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
// "quic" -> {
|
||||||
val quicSetting = streamSettings?.quicSettings ?: return null
|
// val quicSetting = streamSettings?.quicSettings ?: return null
|
||||||
listOf(
|
// listOf(
|
||||||
quicSetting.header.type,
|
// quicSetting.header.type,
|
||||||
quicSetting.security,
|
// quicSetting.security,
|
||||||
quicSetting.key
|
// quicSetting.key
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC.type -> {
|
||||||
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
if (grpcSetting.multiMode == true) "multi" else "gun",
|
if (grpcSetting.multiMode == true) "multi" else "gun",
|
||||||
+53
-14
@@ -1,6 +1,6 @@
|
|||||||
package com.v2ray.ang.fmt
|
package com.v2ray.ang.fmt
|
||||||
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
@@ -29,9 +29,40 @@ open class FmtBase {
|
|||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getItemFormQuery(config: ProfileItem, queryParam: Map<String, String>, allowInsecure: Boolean) {
|
||||||
|
config.network = queryParam["type"] ?: NetworkType.TCP.type
|
||||||
|
//TODO
|
||||||
|
if (config.network == NetworkType.SPLIT_HTTP.type) config.network = NetworkType.XHTTP.type
|
||||||
|
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.xhttpMode = queryParam["mode"]
|
||||||
|
config.xhttpExtra = queryParam["extra"]
|
||||||
|
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
|
||||||
fun getQueryDic(config: ProfileItem): HashMap<String, String> {
|
fun getQueryDic(config: ProfileItem): HashMap<String, String> {
|
||||||
val dicQuery = HashMap<String, String>()
|
val dicQuery = HashMap<String, String>()
|
||||||
|
|
||||||
dicQuery["security"] = config.security?.ifEmpty { "none" }.orEmpty()
|
dicQuery["security"] = config.security?.ifEmpty { "none" }.orEmpty()
|
||||||
config.sni.let { if (it.isNotNullEmpty()) dicQuery["sni"] = it.orEmpty() }
|
config.sni.let { if (it.isNotNullEmpty()) dicQuery["sni"] = it.orEmpty() }
|
||||||
config.alpn.let { if (it.isNotNullEmpty()) dicQuery["alpn"] = it.orEmpty() }
|
config.alpn.let { if (it.isNotNullEmpty()) dicQuery["alpn"] = it.orEmpty() }
|
||||||
@@ -41,37 +72,45 @@ open class FmtBase {
|
|||||||
config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() }
|
config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() }
|
||||||
config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
|
config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
|
||||||
|
|
||||||
dicQuery["type"] = config.network?.ifEmpty { AppConfig.DEFAULT_NETWORK }.orEmpty()
|
val networkType = NetworkType.fromString(config.network)
|
||||||
|
dicQuery["type"] = networkType.type
|
||||||
|
|
||||||
when (config.network) {
|
when (networkType) {
|
||||||
"tcp" -> {
|
NetworkType.TCP -> {
|
||||||
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
||||||
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp" -> {
|
NetworkType.KCP -> {
|
||||||
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
||||||
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
|
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws", "httpupgrade", "splithttp" -> {
|
NetworkType.WS, NetworkType.HTTP_UPGRADE -> {
|
||||||
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
||||||
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
|
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"http", "h2" -> {
|
NetworkType.SPLIT_HTTP, NetworkType.XHTTP -> {
|
||||||
|
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
||||||
|
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
|
||||||
|
config.xhttpMode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
|
||||||
|
config.xhttpExtra.let { if (it.isNotNullEmpty()) dicQuery["extra"] = it.orEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkType.HTTP, NetworkType.H2 -> {
|
||||||
dicQuery["type"] = "http"
|
dicQuery["type"] = "http"
|
||||||
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
|
||||||
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
|
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
// NetworkType.QUIC -> {
|
||||||
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
// dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
|
||||||
config.quicSecurity.let { if (it.isNotNullEmpty()) dicQuery["quicSecurity"] = it.orEmpty() }
|
// config.quicSecurity.let { if (it.isNotNullEmpty()) dicQuery["quicSecurity"] = it.orEmpty() }
|
||||||
config.quicKey.let { if (it.isNotNullEmpty()) dicQuery["key"] = it.orEmpty() }
|
// config.quicKey.let { if (it.isNotNullEmpty()) dicQuery["key"] = it.orEmpty() }
|
||||||
}
|
// }
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC -> {
|
||||||
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
|
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
|
||||||
config.authority.let { if (it.isNotNullEmpty()) dicQuery["authority"] = it.orEmpty() }
|
config.authority.let { if (it.isNotNullEmpty()) dicQuery["authority"] = it.orEmpty() }
|
||||||
config.serviceName.let { if (it.isNotNullEmpty()) dicQuery["serviceName"] = it.orEmpty() }
|
config.serviceName.let { if (it.isNotNullEmpty()) dicQuery["serviceName"] = it.orEmpty() }
|
||||||
+1
-1
@@ -10,7 +10,7 @@ object HttpFmt : FmtBase() {
|
|||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.HTTP)
|
val outboundBean = OutboundBean.create(EConfigType.HTTP)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.get(0)?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = profileItem.server.orEmpty()
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
if (profileItem.username.isNotNullEmpty()) {
|
if (profileItem.username.isNotNullEmpty()) {
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.v2ray.ang.fmt
|
||||||
|
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.NetworkType
|
||||||
|
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? {
|
||||||
|
return parseSip002(str) ?: parseLegacy(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseSip002(str: String): ProfileItem? {
|
||||||
|
val config = ProfileItem.create(EConfigType.SHADOWSOCKS)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.idnHost.isEmpty()) return null
|
||||||
|
if (uri.port <= 0) return null
|
||||||
|
if (uri.userInfo.isNullOrEmpty()) 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(":", limit = 2)
|
||||||
|
} else {
|
||||||
|
Utils.decode(uri.userInfo).split(":", limit = 2)
|
||||||
|
}
|
||||||
|
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 = NetworkType.TCP.type
|
||||||
|
config.headerType = "http"
|
||||||
|
config.host = queryParam["obfs-host"]
|
||||||
|
config.path = queryParam["path"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseLegacy(str: String): ProfileItem? {
|
||||||
|
val config = ProfileItem.create(EConfigType.SHADOWSOCKS)
|
||||||
|
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.server = match.groupValues[3].removeSurrounding("[", "]")
|
||||||
|
config.serverPort = match.groupValues[4]
|
||||||
|
config.password = match.groupValues[2]
|
||||||
|
config.method = match.groupValues[1].lowercase()
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ProfileItem): String {
|
||||||
|
val pw = "${config.method}:${config.password}"
|
||||||
|
|
||||||
|
return toUri(config, Utils.encode(pw), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
|
val outboundBean = OutboundBean.create(EConfigType.SHADOWSOCKS)
|
||||||
|
|
||||||
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
|
server.address = profileItem.server.orEmpty()
|
||||||
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
|
server.password = profileItem.password
|
||||||
|
server.method = profileItem.method
|
||||||
|
}
|
||||||
|
|
||||||
|
return outboundBean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+4
-3
@@ -15,13 +15,14 @@ object SocksFmt : FmtBase() {
|
|||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
if (uri.idnHost.isEmpty()) return null
|
if (uri.idnHost.isEmpty()) return null
|
||||||
|
if (uri.port <= 0) return null
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
if (uri.userInfo?.isEmpty() == false) {
|
if (uri.userInfo?.isEmpty() == false) {
|
||||||
val result = Utils.decode(uri.userInfo).split(":")
|
val result = Utils.decode(uri.userInfo).split(":", limit = 2)
|
||||||
if (result.count() == 2) {
|
if (result.count() == 2) {
|
||||||
config.username = result.first()
|
config.username = result.first()
|
||||||
config.password = result.last()
|
config.password = result.last()
|
||||||
@@ -38,13 +39,13 @@ object SocksFmt : FmtBase() {
|
|||||||
else
|
else
|
||||||
":"
|
":"
|
||||||
|
|
||||||
return toUri(config, pw, null)
|
return toUri(config, Utils.encode(pw), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.SOCKS)
|
val outboundBean = OutboundBean.create(EConfigType.SOCKS)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.get(0)?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = profileItem.server.orEmpty()
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
if (profileItem.username.isNotNullEmpty()) {
|
if (profileItem.username.isNotNullEmpty()) {
|
||||||
+5
-27
@@ -2,6 +2,7 @@ package com.v2ray.ang.fmt
|
|||||||
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
@@ -22,37 +23,14 @@ object TrojanFmt : FmtBase() {
|
|||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
|
||||||
if (uri.rawQuery.isNullOrEmpty()) {
|
if (uri.rawQuery.isNullOrEmpty()) {
|
||||||
|
config.network = NetworkType.TCP.type
|
||||||
config.security = AppConfig.TLS
|
config.security = AppConfig.TLS
|
||||||
config.insecure = allowInsecure
|
config.insecure = allowInsecure
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.network = queryParam["type"] ?: "tcp"
|
getItemFormQuery(config, queryParam, allowInsecure)
|
||||||
config.headerType = queryParam["headerType"]
|
config.security = queryParam["security"] ?: AppConfig.TLS
|
||||||
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
|
return config
|
||||||
@@ -67,7 +45,7 @@ object TrojanFmt : FmtBase() {
|
|||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.TROJAN)
|
val outboundBean = OutboundBean.create(EConfigType.TROJAN)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.get(0)?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = profileItem.server.orEmpty()
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
server.password = profileItem.password
|
server.password = profileItem.password
|
||||||
+5
-26
@@ -6,6 +6,7 @@ import com.v2ray.ang.dto.ProfileItem
|
|||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.util.JsonUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
@@ -25,31 +26,7 @@ object VlessFmt : FmtBase() {
|
|||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
config.method = queryParam["encryption"] ?: "none"
|
config.method = queryParam["encryption"] ?: "none"
|
||||||
|
|
||||||
config.network = queryParam["type"] ?: "tcp"
|
getItemFormQuery(config, queryParam, allowInsecure)
|
||||||
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
|
return config
|
||||||
}
|
}
|
||||||
@@ -65,7 +42,7 @@ object VlessFmt : FmtBase() {
|
|||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.VLESS)
|
val outboundBean = OutboundBean.create(EConfigType.VLESS)
|
||||||
|
|
||||||
outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||||
vnext.address = profileItem.server.orEmpty()
|
vnext.address = profileItem.server.orEmpty()
|
||||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
vnext.users[0].id = profileItem.password.orEmpty()
|
vnext.users[0].id = profileItem.password.orEmpty()
|
||||||
@@ -85,6 +62,8 @@ object VlessFmt : FmtBase() {
|
|||||||
profileItem.serviceName,
|
profileItem.serviceName,
|
||||||
profileItem.authority,
|
profileItem.authority,
|
||||||
)
|
)
|
||||||
|
outboundBean?.streamSettings?.xhttpSettings?.mode = profileItem.xhttpMode
|
||||||
|
outboundBean?.streamSettings?.xhttpSettings?.extra = JsonUtil.parseString(profileItem.xhttpExtra)
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
profileItem.security.orEmpty(),
|
profileItem.security.orEmpty(),
|
||||||
+23
-34
@@ -4,6 +4,7 @@ import android.text.TextUtils
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.dto.VmessQRCode
|
import com.v2ray.ang.dto.VmessQRCode
|
||||||
@@ -47,27 +48,29 @@ object VmessFmt : FmtBase() {
|
|||||||
config.password = vmessQRCode.id
|
config.password = vmessQRCode.id
|
||||||
config.method = if (TextUtils.isEmpty(vmessQRCode.scy)) AppConfig.DEFAULT_SECURITY else vmessQRCode.scy
|
config.method = if (TextUtils.isEmpty(vmessQRCode.scy)) AppConfig.DEFAULT_SECURITY else vmessQRCode.scy
|
||||||
|
|
||||||
config.network = vmessQRCode.net ?: "tcp"
|
config.network = vmessQRCode.net ?: NetworkType.TCP.type
|
||||||
config.headerType = vmessQRCode.type
|
config.headerType = vmessQRCode.type
|
||||||
config.host = vmessQRCode.host
|
config.host = vmessQRCode.host
|
||||||
config.path = vmessQRCode.path
|
config.path = vmessQRCode.path
|
||||||
|
|
||||||
when (config.network) {
|
when (NetworkType.fromString(config.network)) {
|
||||||
"kcp" -> {
|
NetworkType.KCP -> {
|
||||||
config.seed = vmessQRCode.path
|
config.seed = vmessQRCode.path
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
// NetworkType.QUIC -> {
|
||||||
config.quicSecurity = vmessQRCode.host
|
// config.quicSecurity = vmessQRCode.host
|
||||||
config.quicKey = vmessQRCode.path
|
// config.quicKey = vmessQRCode.path
|
||||||
}
|
// }
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC -> {
|
||||||
config.mode = vmessQRCode.type
|
config.mode = vmessQRCode.type
|
||||||
config.serviceName = vmessQRCode.path
|
config.serviceName = vmessQRCode.path
|
||||||
config.authority = vmessQRCode.host
|
config.authority = vmessQRCode.host
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.security = vmessQRCode.tls
|
config.security = vmessQRCode.tls
|
||||||
config.insecure = allowInsecure
|
config.insecure = allowInsecure
|
||||||
config.sni = vmessQRCode.sni
|
config.sni = vmessQRCode.sni
|
||||||
@@ -90,22 +93,24 @@ object VmessFmt : FmtBase() {
|
|||||||
|
|
||||||
vmessQRCode.net = config.network.orEmpty()
|
vmessQRCode.net = config.network.orEmpty()
|
||||||
vmessQRCode.type = config.headerType.orEmpty()
|
vmessQRCode.type = config.headerType.orEmpty()
|
||||||
when (config.network) {
|
when (NetworkType.fromString(config.network)) {
|
||||||
"kcp" -> {
|
NetworkType.KCP -> {
|
||||||
vmessQRCode.path = config.seed.orEmpty()
|
vmessQRCode.path = config.seed.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
// NetworkType.QUIC -> {
|
||||||
vmessQRCode.host = config.quicSecurity.orEmpty()
|
// vmessQRCode.host = config.quicSecurity.orEmpty()
|
||||||
vmessQRCode.path = config.quicKey.orEmpty()
|
// vmessQRCode.path = config.quicKey.orEmpty()
|
||||||
}
|
// }
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC -> {
|
||||||
vmessQRCode.type = config.mode.orEmpty()
|
vmessQRCode.type = config.mode.orEmpty()
|
||||||
vmessQRCode.path = config.serviceName.orEmpty()
|
vmessQRCode.path = config.serviceName.orEmpty()
|
||||||
vmessQRCode.host = config.authority.orEmpty()
|
vmessQRCode.host = config.authority.orEmpty()
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.host.let { if (it.isNotNullEmpty()) vmessQRCode.host = it.orEmpty() }
|
config.host.let { if (it.isNotNullEmpty()) vmessQRCode.host = it.orEmpty() }
|
||||||
config.path.let { if (it.isNotNullEmpty()) vmessQRCode.path = it.orEmpty() }
|
config.path.let { if (it.isNotNullEmpty()) vmessQRCode.path = it.orEmpty() }
|
||||||
|
|
||||||
@@ -119,7 +124,7 @@ object VmessFmt : FmtBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun parseVmessStd(str: String): ProfileItem? {
|
fun parseVmessStd(str: String): ProfileItem? {
|
||||||
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
val allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||||
val config = ProfileItem.create(EConfigType.VMESS)
|
val config = ProfileItem.create(EConfigType.VMESS)
|
||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
@@ -132,23 +137,7 @@ object VmessFmt : FmtBase() {
|
|||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
config.method = AppConfig.DEFAULT_SECURITY
|
config.method = AppConfig.DEFAULT_SECURITY
|
||||||
|
|
||||||
config.network = queryParam["type"] ?: "tcp"
|
getItemFormQuery(config, queryParam, allowInsecure)
|
||||||
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
|
return config
|
||||||
}
|
}
|
||||||
@@ -157,7 +146,7 @@ object VmessFmt : FmtBase() {
|
|||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.VMESS)
|
val outboundBean = OutboundBean.create(EConfigType.VMESS)
|
||||||
|
|
||||||
outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||||
vnext.address = profileItem.server.orEmpty()
|
vnext.address = profileItem.server.orEmpty()
|
||||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
vnext.users[0].id = profileItem.password.orEmpty()
|
vnext.users[0].id = profileItem.password.orEmpty()
|
||||||
+58
-29
@@ -22,9 +22,10 @@ object WireguardFmt : FmtBase() {
|
|||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
config.secretKey = uri.userInfo
|
config.secretKey = uri.userInfo.orEmpty()
|
||||||
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
|
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
|
||||||
config.publicKey = queryParam["publickey"].orEmpty()
|
config.publicKey = queryParam["publickey"].orEmpty()
|
||||||
|
config.preSharedKey = queryParam["presharedkey"].orEmpty()
|
||||||
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
config.reserved = (queryParam["reserved"] ?: "0,0,0")
|
config.reserved = (queryParam["reserved"] ?: "0,0,0")
|
||||||
|
|
||||||
@@ -33,33 +34,75 @@ object WireguardFmt : FmtBase() {
|
|||||||
|
|
||||||
fun parseWireguardConfFile(str: String): ProfileItem? {
|
fun parseWireguardConfFile(str: String): ProfileItem? {
|
||||||
val config = ProfileItem.create(EConfigType.WIREGUARD)
|
val config = ProfileItem.create(EConfigType.WIREGUARD)
|
||||||
val queryParam: MutableMap<String, String> = mutableMapOf()
|
|
||||||
|
val interfaceParams: MutableMap<String, String> = mutableMapOf()
|
||||||
|
val peerParams: MutableMap<String, String> = mutableMapOf()
|
||||||
|
|
||||||
var currentSection: String? = null
|
var currentSection: String? = null
|
||||||
|
|
||||||
str.lines().forEach { line ->
|
str.lines().forEach { line ->
|
||||||
val trimmedLine = line.trim()
|
val trimmedLine = line.trim()
|
||||||
|
|
||||||
|
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
|
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
|
||||||
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
|
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
|
||||||
trimmedLine.isBlank() || trimmedLine.startsWith("#") -> Unit // Skip blank lines or comments
|
else -> {
|
||||||
currentSection != null -> {
|
if (currentSection != null) {
|
||||||
val (key, value) = trimmedLine.split("=").map { it.trim() }
|
val parts = trimmedLine.split("=", limit = 2).map { it.trim() }
|
||||||
queryParam[key.lowercase()] = value // Store the key in lowercase for case-insensitivity
|
if (parts.size == 2) {
|
||||||
|
val key = parts[0].lowercase()
|
||||||
|
val value = parts[1]
|
||||||
|
when (currentSection) {
|
||||||
|
"Interface" -> interfaceParams[key] = value
|
||||||
|
"Peer" -> peerParams[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.secretKey = queryParam["privatekey"].orEmpty()
|
config.secretKey = interfaceParams["privatekey"].orEmpty()
|
||||||
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
|
config.remarks = System.currentTimeMillis().toString()
|
||||||
config.publicKey = queryParam["publickey"].orEmpty()
|
config.localAddress = interfaceParams["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
config.mtu = Utils.parseInt(interfaceParams["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
config.reserved = (queryParam["reserved"] ?: "0,0,0")
|
config.publicKey = peerParams["publickey"].orEmpty()
|
||||||
|
config.preSharedKey = peerParams["presharedkey"].orEmpty()
|
||||||
|
val endpoint = peerParams["endpoint"].orEmpty()
|
||||||
|
val endpointParts = endpoint.split(":", limit = 2)
|
||||||
|
if (endpointParts.size == 2) {
|
||||||
|
config.server = endpointParts[0]
|
||||||
|
config.serverPort = endpointParts[1]
|
||||||
|
} else {
|
||||||
|
config.server = endpoint
|
||||||
|
config.serverPort = ""
|
||||||
|
}
|
||||||
|
config.reserved = peerParams["reserved"] ?: "0,0,0"
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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?.firstOrNull()?.let { peer ->
|
||||||
|
peer.publicKey = profileItem.publicKey.orEmpty()
|
||||||
|
peer.preSharedKey = profileItem.preSharedKey.orEmpty()
|
||||||
|
peer.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
|
||||||
|
}
|
||||||
|
wireguard.mtu = profileItem.mtu
|
||||||
|
wireguard.reserved = profileItem.reserved?.split(",")?.map { it.toInt() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return outboundBean
|
||||||
|
}
|
||||||
|
|
||||||
fun toUri(config: ProfileItem): String {
|
fun toUri(config: ProfileItem): String {
|
||||||
val dicQuery = HashMap<String, String>()
|
val dicQuery = HashMap<String, String>()
|
||||||
@@ -72,24 +115,10 @@ object WireguardFmt : FmtBase() {
|
|||||||
if (config.mtu != null) {
|
if (config.mtu != null) {
|
||||||
dicQuery["mtu"] = config.mtu.toString()
|
dicQuery["mtu"] = config.mtu.toString()
|
||||||
}
|
}
|
||||||
|
if (config.preSharedKey != null) {
|
||||||
|
dicQuery["presharedkey"] = Utils.removeWhiteSpace(config.preSharedKey).orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
return toUri(config, config.secretKey, dicQuery)
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
+2
-2
@@ -277,10 +277,10 @@ object AngConfigManager {
|
|||||||
if (serverList.isNotEmpty()) {
|
if (serverList.isNotEmpty()) {
|
||||||
var count = 0
|
var count = 0
|
||||||
for (srv in serverList.reversed()) {
|
for (srv in serverList.reversed()) {
|
||||||
val config = CustomFmt.parse(server) ?: continue
|
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: continue
|
||||||
config.subscriptionId = subid
|
config.subscriptionId = subid
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
|
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv)?:"")
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
+6
-5
@@ -5,6 +5,7 @@ import com.tencent.mmkv.MMKV
|
|||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.ServerConfig
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
||||||
@@ -19,7 +20,7 @@ object MigrateManager {
|
|||||||
if (serverStorage.count().toInt() == 0) {
|
if (serverStorage.count().toInt() == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var serverList = serverStorage.allKeys() ?: return false
|
val serverList = serverStorage.allKeys() ?: return false
|
||||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + serverList.count())
|
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + serverList.count())
|
||||||
|
|
||||||
for (guid in serverList) {
|
for (guid in serverList) {
|
||||||
@@ -70,10 +71,10 @@ object MigrateManager {
|
|||||||
config.serverPort = outbound.getServerPort().toString()
|
config.serverPort = outbound.getServerPort().toString()
|
||||||
config.method = outbound.getSecurityEncryption()
|
config.method = outbound.getSecurityEncryption()
|
||||||
config.password = outbound.getPassword()
|
config.password = outbound.getPassword()
|
||||||
config.flow = outbound?.settings?.vnext?.get(0)?.users[0]?.flow ?: outbound?.settings?.servers?.get(0)?.flow
|
config.flow = outbound?.settings?.vnext?.first()?.users?.first()?.flow ?: outbound?.settings?.servers?.first()?.flow
|
||||||
|
|
||||||
|
config.network = outbound?.streamSettings?.network ?: NetworkType.TCP.type
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
config.network = "tcp"
|
|
||||||
config.headerType = transportDetails[0].orEmpty()
|
config.headerType = transportDetails[0].orEmpty()
|
||||||
config.host = transportDetails[1].orEmpty()
|
config.host = transportDetails[1].orEmpty()
|
||||||
config.path = transportDetails[2].orEmpty()
|
config.path = transportDetails[2].orEmpty()
|
||||||
@@ -107,7 +108,7 @@ object MigrateManager {
|
|||||||
config.remarks = configOld.remarks
|
config.remarks = configOld.remarks
|
||||||
config.server = outbound.getServerAddress()
|
config.server = outbound.getServerAddress()
|
||||||
config.serverPort = outbound.getServerPort().toString()
|
config.serverPort = outbound.getServerPort().toString()
|
||||||
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
|
config.username = outbound.settings?.servers?.first()?.users?.first()?.user
|
||||||
config.password = outbound.getPassword()
|
config.password = outbound.getPassword()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
@@ -120,7 +121,7 @@ object MigrateManager {
|
|||||||
config.remarks = configOld.remarks
|
config.remarks = configOld.remarks
|
||||||
config.server = outbound.getServerAddress()
|
config.server = outbound.getServerAddress()
|
||||||
config.serverPort = outbound.getServerPort().toString()
|
config.serverPort = outbound.getServerPort().toString()
|
||||||
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
|
config.username = outbound.settings?.servers?.first()?.users?.first()?.user
|
||||||
config.password = outbound.getPassword()
|
config.password = outbound.getPassword()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
+1
-1
@@ -336,7 +336,7 @@ object MmkvManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun decodeSettingsBool(key: String): Boolean {
|
fun decodeSettingsBool(key: String): Boolean {
|
||||||
return settingsStorage.decodeBool(key)
|
return settingsStorage.decodeBool(key,false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {
|
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {
|
||||||
+3
-3
@@ -43,12 +43,12 @@ object SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun resetRoutingRulesets(context: Context, index: Int) {
|
fun resetRoutingRulesetsFromPresets(context: Context, index: Int) {
|
||||||
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
|
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
|
||||||
resetRoutingRulesetsCommon(rulesetList)
|
resetRoutingRulesetsCommon(rulesetList)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetRoutingRulesetsFromClipboard(content: String?): Boolean {
|
fun resetRoutingRulesets(content: String?): Boolean {
|
||||||
if (content.isNullOrEmpty()) {
|
if (content.isNullOrEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ object SettingsManager {
|
|||||||
if (rulesetList.isNullOrEmpty()) return
|
if (rulesetList.isNullOrEmpty()) return
|
||||||
|
|
||||||
if (index < 0 || index >= rulesetList.count()) {
|
if (index < 0 || index >= rulesetList.count()) {
|
||||||
rulesetList.add(ruleset)
|
rulesetList.add(0, ruleset)
|
||||||
} else {
|
} else {
|
||||||
rulesetList[index] = ruleset
|
rulesetList[index] = ruleset
|
||||||
}
|
}
|
||||||
+15
-12
@@ -8,12 +8,16 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|||||||
import com.v2ray.ang.AppConfig.DEFAULT_NETWORK
|
import com.v2ray.ang.AppConfig.DEFAULT_NETWORK
|
||||||
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
|
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
|
||||||
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
|
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
|
||||||
|
import com.v2ray.ang.AppConfig.DNS_CLOUDFLARE_ADDRESSES
|
||||||
|
import com.v2ray.ang.AppConfig.DNS_CLOUDFLARE_DOMAIN
|
||||||
|
import com.v2ray.ang.AppConfig.DNS_DNSPOD_ADDRESSES
|
||||||
|
import com.v2ray.ang.AppConfig.DNS_DNSPOD_DOMAIN
|
||||||
import com.v2ray.ang.AppConfig.DNS_GOOGLE_ADDRESSES
|
import com.v2ray.ang.AppConfig.DNS_GOOGLE_ADDRESSES
|
||||||
import com.v2ray.ang.AppConfig.DNS_GOOGLE_DOMAIN
|
import com.v2ray.ang.AppConfig.DNS_GOOGLE_DOMAIN
|
||||||
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_ADDRESSES
|
import com.v2ray.ang.AppConfig.DNS_QUAD9_ADDRESSES
|
||||||
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_DOMAIN
|
import com.v2ray.ang.AppConfig.DNS_QUAD9_DOMAIN
|
||||||
import com.v2ray.ang.AppConfig.DNS_PUB_ADDRESSES
|
import com.v2ray.ang.AppConfig.DNS_YANDEX_ADDRESSES
|
||||||
import com.v2ray.ang.AppConfig.DNS_PUB_DOMAIN
|
import com.v2ray.ang.AppConfig.DNS_YANDEX_DOMAIN
|
||||||
import com.v2ray.ang.AppConfig.GEOIP_CN
|
import com.v2ray.ang.AppConfig.GEOIP_CN
|
||||||
import com.v2ray.ang.AppConfig.GEOSITE_CN
|
import com.v2ray.ang.AppConfig.GEOSITE_CN
|
||||||
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
||||||
@@ -57,7 +61,7 @@ object V2rayConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val result = getV2rayNonCustomConfig(context, config)
|
val result = getV2rayNonCustomConfig(context, config)
|
||||||
Log.d(ANG_PACKAGE, result.content)
|
//Log.d(ANG_PACKAGE, result.content)
|
||||||
result.guid = guid
|
result.guid = guid
|
||||||
return result
|
return result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -334,9 +338,7 @@ object V2rayConfigManager {
|
|||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
address = remoteDns.first(),
|
address = remoteDns.first(),
|
||||||
port = 53,
|
|
||||||
domains = proxyDomain,
|
domains = proxyDomain,
|
||||||
expectIPs = null
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -350,7 +352,6 @@ object V2rayConfigManager {
|
|||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
address = domesticDns.first(),
|
address = domesticDns.first(),
|
||||||
port = 53,
|
|
||||||
domains = directDomain,
|
domains = directDomain,
|
||||||
expectIPs = if (isCnRoutingMode) geoipCn else null,
|
expectIPs = if (isCnRoutingMode) geoipCn else null,
|
||||||
skipFallback = true
|
skipFallback = true
|
||||||
@@ -379,10 +380,12 @@ object V2rayConfigManager {
|
|||||||
hosts[GOOGLEAPIS_CN_DOMAIN] = GOOGLEAPIS_COM_DOMAIN
|
hosts[GOOGLEAPIS_CN_DOMAIN] = GOOGLEAPIS_COM_DOMAIN
|
||||||
|
|
||||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||||
hosts[DNS_PUB_DOMAIN] = DNS_PUB_ADDRESSES
|
|
||||||
hosts[DNS_ALIDNS_DOMAIN] = DNS_ALIDNS_ADDRESSES
|
hosts[DNS_ALIDNS_DOMAIN] = DNS_ALIDNS_ADDRESSES
|
||||||
hosts[DNS_ONE_ONE_DOMAIN] = DNS_ONE_ONE_ADDRESSES
|
hosts[DNS_CLOUDFLARE_DOMAIN] = DNS_CLOUDFLARE_ADDRESSES
|
||||||
|
hosts[DNS_DNSPOD_DOMAIN] = DNS_DNSPOD_ADDRESSES
|
||||||
hosts[DNS_GOOGLE_DOMAIN] = DNS_GOOGLE_ADDRESSES
|
hosts[DNS_GOOGLE_DOMAIN] = DNS_GOOGLE_ADDRESSES
|
||||||
|
hosts[DNS_QUAD9_DOMAIN] = DNS_QUAD9_ADDRESSES
|
||||||
|
hosts[DNS_YANDEX_DOMAIN] = DNS_YANDEX_ADDRESSES
|
||||||
|
|
||||||
|
|
||||||
// DNS dns对象
|
// DNS dns对象
|
||||||
@@ -422,7 +425,7 @@ object V2rayConfigManager {
|
|||||||
) {
|
) {
|
||||||
muxEnabled = false
|
muxEnabled = false
|
||||||
} else if (protocol.equals(EConfigType.VLESS.name, true)
|
} else if (protocol.equals(EConfigType.VLESS.name, true)
|
||||||
&& outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.isNotEmpty() == true
|
&& outbound.settings?.vnext?.first()?.users?.first()?.flow?.isNotEmpty() == true
|
||||||
) {
|
) {
|
||||||
muxEnabled = false
|
muxEnabled = false
|
||||||
}
|
}
|
||||||
@@ -458,7 +461,7 @@ object V2rayConfigManager {
|
|||||||
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
||||||
|
|
||||||
val requestString: String by lazy {
|
val requestString: String by lazy {
|
||||||
"""{"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"}}"""
|
"""{"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"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
|
||||||
}
|
}
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
|
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
|
||||||
requestString,
|
requestString,
|
||||||
+16
-21
@@ -13,46 +13,41 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package com.v2ray.ang.helper
|
||||||
package com.v2ray.ang.helper;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
|
* Interface to listen for a move or dismissal event from a [ItemTouchHelper.Callback].
|
||||||
*
|
*
|
||||||
* @author Paul Burke (ipaulpro)
|
* @author Paul Burke (ipaulpro)
|
||||||
*/
|
*/
|
||||||
public interface ItemTouchHelperAdapter {
|
interface ItemTouchHelperAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an item has been dragged far enough to trigger a move. This is called every time
|
* Called when an item has been dragged far enough to trigger a move. This is called every time
|
||||||
* an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/>
|
* an item is shifted, and **not** at the end of a "drop" event.<br></br>
|
||||||
* <br/>
|
* <br></br>
|
||||||
* Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
|
* Implementations should call [RecyclerView.Adapter.notifyItemMoved] after
|
||||||
* adjusting the underlying data to reflect this move.
|
* adjusting the underlying data to reflect this move.
|
||||||
*
|
*
|
||||||
* @param fromPosition The start position of the moved item.
|
* @param fromPosition The start position of the moved item.
|
||||||
* @param toPosition Then resolved position of the moved item.
|
* @param toPosition Then resolved position of the moved item.
|
||||||
* @return True if the item was moved to the new adapter position.
|
* @return True if the item was moved to the new adapter position.
|
||||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
* @see RecyclerView.getAdapterPositionFor
|
||||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
* @see RecyclerView.ViewHolder.getAdapterPosition
|
||||||
*/
|
*/
|
||||||
boolean onItemMove(int fromPosition, int toPosition);
|
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
|
||||||
|
|
||||||
|
|
||||||
void onItemMoveCompleted();
|
fun onItemMoveCompleted()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an item has been dismissed by a swipe.<br/>
|
* Called when an item has been dismissed by a swipe.<br></br>
|
||||||
* <br/>
|
* <br></br>
|
||||||
* Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after
|
* Implementations should call [RecyclerView.Adapter.notifyItemRemoved] after
|
||||||
* adjusting the underlying data to reflect this removal.
|
* adjusting the underlying data to reflect this removal.
|
||||||
*
|
*
|
||||||
* @param position The position of the item dismissed.
|
* @param position The position of the item dismissed.
|
||||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
* @see RecyclerView.getAdapterPositionFor
|
||||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
* @see RecyclerView.ViewHolder.getAdapterPosition
|
||||||
*/
|
*/
|
||||||
void onItemDismiss(int position);
|
fun onItemDismiss(position: Int)
|
||||||
}
|
}
|
||||||
+8
-11
@@ -13,29 +13,26 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package com.v2ray.ang.helper
|
||||||
|
|
||||||
package com.v2ray.ang.helper;
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to notify an item ViewHolder of relevant callbacks from {@link
|
* Interface to notify an item ViewHolder of relevant callbacks from [ ].
|
||||||
* ItemTouchHelper.Callback}.
|
|
||||||
*
|
*
|
||||||
* @author Paul Burke (ipaulpro)
|
* @author Paul Burke (ipaulpro)
|
||||||
*/
|
*/
|
||||||
public interface ItemTouchHelperViewHolder {
|
interface ItemTouchHelperViewHolder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
|
* Called when the [ItemTouchHelper] first registers an item as being moved or swiped.
|
||||||
* Implementations should update the item view to indicate it's active state.
|
* Implementations should update the item view to indicate it's active state.
|
||||||
*/
|
*/
|
||||||
void onItemSelected();
|
fun onItemSelected()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
|
* Called when the [ItemTouchHelper] has completed the move or swipe, and the active item
|
||||||
* state should be cleared.
|
* state should be cleared.
|
||||||
*/
|
*/
|
||||||
void onItemClear();
|
fun onItemClear()
|
||||||
}
|
}
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Paul Burke
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
|
|
||||||
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
|
|
||||||
* </br/>
|
|
||||||
* Expects the <code>RecyclerView.Adapter</code> to listen for {@link
|
|
||||||
* ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
|
|
||||||
* {@link ItemTouchHelperViewHolder}.
|
|
||||||
*
|
|
||||||
* @author Paul Burke (ipaulpro)
|
|
||||||
*/
|
|
||||||
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|
||||||
|
|
||||||
private static final float ALPHA_FULL = 1.0f;
|
|
||||||
private static final float SWIPE_THRESHOLD = 0.25f;
|
|
||||||
private static final long ANIMATION_DURATION = 200;
|
|
||||||
|
|
||||||
private final ItemTouchHelperAdapter mAdapter;
|
|
||||||
private ValueAnimator mReturnAnimator;
|
|
||||||
|
|
||||||
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
|
|
||||||
mAdapter = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLongPressDragEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isItemViewSwipeEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
|
||||||
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
|
|
||||||
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
|
||||||
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
|
|
||||||
return makeMovementFlags(dragFlags, swipeFlags);
|
|
||||||
} else {
|
|
||||||
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
|
|
||||||
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
|
|
||||||
return makeMovementFlags(dragFlags, swipeFlags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, @NonNull RecyclerView.ViewHolder target) {
|
|
||||||
if (source.getItemViewType() != target.getItemViewType()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mAdapter.onItemMove(source.getBindingAdapterPosition(), target.getBindingAdapterPosition());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
|
||||||
// 不执行删除操作,仅返回项目到原位
|
|
||||||
returnViewToOriginalPosition(viewHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
|
|
||||||
@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;
|
|
||||||
|
|
||||||
viewHolder.itemView.setTranslationX(translationX);
|
|
||||||
viewHolder.itemView.setAlpha(alpha);
|
|
||||||
|
|
||||||
if (swipeAmount >= maxSwipeDistance && isCurrentlyActive) {
|
|
||||||
returnViewToOriginalPosition(viewHolder);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void returnViewToOriginalPosition(RecyclerView.ViewHolder viewHolder) {
|
|
||||||
if (mReturnAnimator != null && mReturnAnimator.isRunning()) {
|
|
||||||
mReturnAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mReturnAnimator = ValueAnimator.ofFloat(viewHolder.itemView.getTranslationX(), 0f);
|
|
||||||
mReturnAnimator.addUpdateListener(animation -> {
|
|
||||||
float value = (float) animation.getAnimatedValue();
|
|
||||||
viewHolder.itemView.setTranslationX(value);
|
|
||||||
viewHolder.itemView.setAlpha(1f - Math.abs(value) / (viewHolder.itemView.getWidth() * SWIPE_THRESHOLD));
|
|
||||||
});
|
|
||||||
mReturnAnimator.setInterpolator(new DecelerateInterpolator());
|
|
||||||
mReturnAnimator.setDuration(ANIMATION_DURATION);
|
|
||||||
mReturnAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
|
|
||||||
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
|
|
||||||
if (viewHolder instanceof ItemTouchHelperViewHolder) {
|
|
||||||
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
|
|
||||||
itemViewHolder.onItemSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onSelectedChanged(viewHolder, actionState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
|
||||||
super.clearView(recyclerView, viewHolder);
|
|
||||||
viewHolder.itemView.setAlpha(ALPHA_FULL);
|
|
||||||
if (viewHolder instanceof ItemTouchHelperViewHolder) {
|
|
||||||
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
|
|
||||||
itemViewHolder.onItemClear();
|
|
||||||
}
|
|
||||||
mAdapter.onItemMoveCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
|
|
||||||
return 1.1f; // 设置一个大于1的值,确保不会触发默认的滑动删除操作
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getSwipeEscapeVelocity(float defaultValue) {
|
|
||||||
return defaultValue * 10; // 增加滑动逃逸速度,使得更难触发滑动
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Paul Burke
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.v2ray.ang.helper
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.animation.ValueAnimator.AnimatorUpdateListener
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of [ItemTouchHelper.Callback] that enables basic drag & drop and
|
||||||
|
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br></br>
|
||||||
|
*
|
||||||
|
* Expects the `RecyclerView.Adapter` to listen for [ ] callbacks and the `RecyclerView.ViewHolder` to implement
|
||||||
|
* [ItemTouchHelperViewHolder].
|
||||||
|
*
|
||||||
|
* @author Paul Burke (ipaulpro)
|
||||||
|
*/
|
||||||
|
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
|
||||||
|
private var mReturnAnimator: ValueAnimator? = null
|
||||||
|
|
||||||
|
override fun isLongPressDragEnabled(): Boolean = true
|
||||||
|
|
||||||
|
override fun isItemViewSwipeEnabled(): Boolean = true
|
||||||
|
|
||||||
|
override fun getMovementFlags(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder
|
||||||
|
): Int {
|
||||||
|
val dragFlags: Int
|
||||||
|
val swipeFlags: Int
|
||||||
|
if (recyclerView.layoutManager is GridLayoutManager) {
|
||||||
|
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
||||||
|
swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
||||||
|
} else {
|
||||||
|
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
|
swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
||||||
|
}
|
||||||
|
return makeMovementFlags(dragFlags, swipeFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
source: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
return if (source.itemViewType != target.itemViewType) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
mAdapter.onItemMove(source.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
// Do not delete; simply return item to original position
|
||||||
|
returnViewToOriginalPosition(viewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChildDraw(
|
||||||
|
c: Canvas, recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
|
val maxSwipeDistance = viewHolder.itemView.width * SWIPE_THRESHOLD
|
||||||
|
val swipeAmount = abs(dX)
|
||||||
|
val direction = sign(dX)
|
||||||
|
|
||||||
|
// Limit maximum swipe distance
|
||||||
|
val translationX = min(swipeAmount, maxSwipeDistance) * direction
|
||||||
|
val alpha = ALPHA_FULL - min(swipeAmount, maxSwipeDistance) / maxSwipeDistance
|
||||||
|
|
||||||
|
viewHolder.itemView.translationX = translationX
|
||||||
|
viewHolder.itemView.alpha = alpha
|
||||||
|
|
||||||
|
if (swipeAmount >= maxSwipeDistance && isCurrentlyActive) {
|
||||||
|
returnViewToOriginalPosition(viewHolder)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnViewToOriginalPosition(viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
mReturnAnimator?.takeIf { it.isRunning }?.cancel()
|
||||||
|
|
||||||
|
mReturnAnimator = ValueAnimator.ofFloat(viewHolder.itemView.translationX, 0f).apply {
|
||||||
|
addUpdateListener { animation ->
|
||||||
|
val value = animation.animatedValue as Float
|
||||||
|
viewHolder.itemView.translationX = value
|
||||||
|
viewHolder.itemView.alpha = (1f - abs(value) / (viewHolder.itemView.width * SWIPE_THRESHOLD))
|
||||||
|
}
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
duration = ANIMATION_DURATION
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||||
|
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && viewHolder is ItemTouchHelperViewHolder) {
|
||||||
|
viewHolder.onItemSelected()
|
||||||
|
}
|
||||||
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder)
|
||||||
|
viewHolder.itemView.alpha = ALPHA_FULL
|
||||||
|
if (viewHolder is ItemTouchHelperViewHolder) {
|
||||||
|
viewHolder.onItemClear()
|
||||||
|
}
|
||||||
|
mAdapter.onItemMoveCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
|
||||||
|
return 1.1f // Set a value greater than 1 to prevent default swipe delete
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSwipeEscapeVelocity(defaultValue: Float): Float {
|
||||||
|
return defaultValue * 10 // Increase swipe escape velocity to make swipe harder to trigger
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ALPHA_FULL = 1.0f
|
||||||
|
private const val SWIPE_THRESHOLD = 0.25f
|
||||||
|
private const val ANIMATION_DURATION: Long = 200
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class BootReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
//Check if context is not null and action is the one we want
|
||||||
|
if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) return
|
||||||
|
//Check if flag is true and a server is selected
|
||||||
|
if (!MmkvManager.decodeStartOnBoot() || MmkvManager.getSelectServer().isNullOrEmpty()) return
|
||||||
|
//Start v2ray
|
||||||
|
V2RayServiceManager.startV2Ray(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -16,9 +16,9 @@ class TaskerReceiver : BroadcastReceiver() {
|
|||||||
try {
|
try {
|
||||||
val bundle = intent?.getBundleExtra(AppConfig.TASKER_EXTRA_BUNDLE)
|
val bundle = intent?.getBundleExtra(AppConfig.TASKER_EXTRA_BUNDLE)
|
||||||
val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false)
|
val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false)
|
||||||
val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "")
|
val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID).orEmpty()
|
||||||
|
|
||||||
if (switch == null || guid == null || TextUtils.isEmpty(guid)) {
|
if (switch == null || TextUtils.isEmpty(guid)) {
|
||||||
return
|
return
|
||||||
} else if (switch) {
|
} else if (switch) {
|
||||||
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
||||||
+7
-8
@@ -8,11 +8,10 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ProcessService {
|
class ProcessService {
|
||||||
private val TAG = ANG_PACKAGE
|
|
||||||
private var process: Process? = null
|
private var process: Process? = null
|
||||||
|
|
||||||
fun runProcess(context: Context, cmd: MutableList<String>) {
|
fun runProcess(context: Context, cmd: MutableList<String>) {
|
||||||
Log.d(TAG, cmd.toString())
|
Log.d(ANG_PACKAGE, cmd.toString())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val proBuilder = ProcessBuilder(cmd)
|
val proBuilder = ProcessBuilder(cmd)
|
||||||
@@ -23,23 +22,23 @@ class ProcessService {
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
Thread.sleep(50L)
|
Thread.sleep(50L)
|
||||||
Log.d(TAG, "runProcess check")
|
Log.d(ANG_PACKAGE, "runProcess check")
|
||||||
process?.waitFor()
|
process?.waitFor()
|
||||||
Log.d(TAG, "runProcess exited")
|
Log.d(ANG_PACKAGE, "runProcess exited")
|
||||||
}
|
}
|
||||||
Log.d(TAG, process.toString())
|
Log.d(ANG_PACKAGE, process.toString())
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopProcess() {
|
fun stopProcess() {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "runProcess destroy")
|
Log.d(ANG_PACKAGE, "runProcess destroy")
|
||||||
process?.destroy()
|
process?.destroy()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-8
@@ -9,6 +9,7 @@ import android.graphics.drawable.Icon
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.util.MessageUtil
|
import com.v2ray.ang.util.MessageUtil
|
||||||
@@ -32,24 +33,31 @@ class QSTileService : TileService() {
|
|||||||
qsTile?.updateTile()
|
qsTile?.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||||
|
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
|
||||||
|
*/
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
|
||||||
setState(Tile.STATE_INACTIVE)
|
setState(Tile.STATE_INACTIVE)
|
||||||
mMsgReceive = ReceiveMessageHandler(this)
|
mMsgReceive = ReceiveMessageHandler(this)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
||||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), RECEIVER_EXPORTED)
|
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||||
} else {
|
|
||||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
super.onStopListening()
|
super.onStopListening()
|
||||||
|
|
||||||
unregisterReceiver(mMsgReceive)
|
try {
|
||||||
mMsgReceive = null
|
applicationContext.unregisterReceiver(mMsgReceive)
|
||||||
|
mMsgReceive = null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
+9
-7
@@ -13,6 +13,7 @@ import android.os.Build
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
@@ -65,7 +66,7 @@ object V2RayServiceManager {
|
|||||||
if (v2rayPoint.isRunning) return
|
if (v2rayPoint.isRunning) return
|
||||||
val guid = MmkvManager.getSelectServer() ?: return
|
val guid = MmkvManager.getSelectServer() ?: return
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||||
if (!Utils.isValidUrl(config.server) && !Utils.isValidUrl(config.server)) return
|
if (!Utils.isValidUrl(config.server) && !Utils.isIpAddress(config.server)) return
|
||||||
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||||
// if (!result.status) return
|
// if (!result.status) return
|
||||||
|
|
||||||
@@ -126,6 +127,11 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||||
|
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
|
||||||
|
*/
|
||||||
|
|
||||||
fun startV2rayPoint() {
|
fun startV2rayPoint() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
val guid = MmkvManager.getSelectServer() ?: return
|
val guid = MmkvManager.getSelectServer() ?: return
|
||||||
@@ -142,11 +148,7 @@ object V2RayServiceManager {
|
|||||||
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||||
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||||
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
|
|
||||||
} else {
|
|
||||||
service.registerReceiver(mMsgReceive, mFilter)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
@@ -156,7 +158,7 @@ object V2RayServiceManager {
|
|||||||
currentConfig = config
|
currentConfig = config
|
||||||
|
|
||||||
try {
|
try {
|
||||||
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true)
|
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
+1
-1
@@ -24,7 +24,7 @@ import libv2ray.Libv2ray
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class V2RayTestService : Service() {
|
class V2RayTestService : Service() {
|
||||||
private val realTestScope by lazy { CoroutineScope(Executors.newFixedThreadPool(10).asCoroutineDispatcher()) }
|
private val realTestScope by lazy { CoroutineScope(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()).asCoroutineDispatcher()) }
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
+8
-6
@@ -17,6 +17,7 @@ import android.os.StrictMode
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -153,9 +154,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
||||||
|
|
||||||
val selfPackageName = BuildConfig.APPLICATION_ID
|
val selfPackageName = BuildConfig.APPLICATION_ID
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)) {
|
||||||
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||||
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS) == true
|
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
|
||||||
//process self package
|
//process self package
|
||||||
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
|
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
|
||||||
apps?.forEach {
|
apps?.forEach {
|
||||||
@@ -165,6 +166,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
else
|
else
|
||||||
builder.addAllowedApplication(it)
|
builder.addAllowedApplication(it)
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
Log.d(ANG_PACKAGE, "setup error : --${e.localizedMessage}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -215,11 +217,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
"--loglevel", "notice"
|
"--loglevel", "notice"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
|
||||||
cmd.add("--netif-ip6addr")
|
cmd.add("--netif-ip6addr")
|
||||||
cmd.add(PRIVATE_VLAN6_ROUTER)
|
cmd.add(PRIVATE_VLAN6_ROUTER)
|
||||||
}
|
}
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
|
||||||
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
||||||
cmd.add("--dnsgw")
|
cmd.add("--dnsgw")
|
||||||
cmd.add("$LOOPBACK:${localDnsPort}")
|
cmd.add("$LOOPBACK:${localDnsPort}")
|
||||||
@@ -232,7 +234,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
process = proBuilder
|
process = proBuilder
|
||||||
.directory(applicationContext.filesDir)
|
.directory(applicationContext.filesDir)
|
||||||
.start()
|
.start()
|
||||||
Thread(Runnable {
|
Thread {
|
||||||
Log.d(packageName, "$TUN2SOCKS check")
|
Log.d(packageName, "$TUN2SOCKS check")
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
Log.d(packageName, "$TUN2SOCKS exited")
|
Log.d(packageName, "$TUN2SOCKS exited")
|
||||||
@@ -240,7 +242,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
Log.d(packageName, "$TUN2SOCKS restart")
|
Log.d(packageName, "$TUN2SOCKS restart")
|
||||||
runTun2socks()
|
runTun2socks()
|
||||||
}
|
}
|
||||||
}).start()
|
}.start()
|
||||||
Log.d(packageName, process.toString())
|
Log.d(packageName, process.toString())
|
||||||
|
|
||||||
sendFd()
|
sendFd()
|
||||||
+1
-1
@@ -133,7 +133,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
.subscribe {
|
.subscribe {
|
||||||
if (!it)
|
if (!it)
|
||||||
toast(R.string.toast_permission_denied)
|
toast(R.string.toast_permission_denied_notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+46
-3
@@ -1,15 +1,18 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
||||||
@@ -83,13 +86,13 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_rulesets -> {
|
R.id.import_predefined_rulesets -> {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
AlertDialog.Builder(this).setItems(preset_rulesets.asList().toTypedArray()) { _, i ->
|
AlertDialog.Builder(this).setItems(preset_rulesets.asList().toTypedArray()) { _, i ->
|
||||||
try {
|
try {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
SettingsManager.resetRoutingRulesets(this@RoutingSettingActivity, i)
|
SettingsManager.resetRoutingRulesetsFromPresets(this@RoutingSettingActivity, i)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
refreshData()
|
refreshData()
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
@@ -120,7 +123,7 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
return@setPositiveButton
|
return@setPositiveButton
|
||||||
}
|
}
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val result = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
|
val result = SettingsManager.resetRoutingRulesets(clipboard)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (result) {
|
if (result) {
|
||||||
refreshData()
|
refreshData()
|
||||||
@@ -138,6 +141,18 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.import_rulesets_from_qrcode -> {
|
||||||
|
RxPermissions(this)
|
||||||
|
.request(Manifest.permission.CAMERA)
|
||||||
|
.subscribe {
|
||||||
|
if (it)
|
||||||
|
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
else
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
R.id.export_rulesets_to_clipboard -> {
|
R.id.export_rulesets_to_clipboard -> {
|
||||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||||
@@ -153,6 +168,34 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scanQRcodeForRulesets = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == RESULT_OK) {
|
||||||
|
importRulesetsFromQRcode(it.data?.getStringExtra("SCAN_RESULT"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importRulesetsFromQRcode(qrcode: String?): Boolean {
|
||||||
|
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val result = SettingsManager.resetRoutingRulesets(qrcode)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (result) {
|
||||||
|
refreshData()
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun refreshData() {
|
fun refreshData() {
|
||||||
rulesets.clear()
|
rulesets.clear()
|
||||||
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
||||||
+100
-53
@@ -24,7 +24,9 @@ import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
|||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
@@ -79,6 +81,10 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val alpns: Array<out String> by lazy {
|
private val alpns: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.streamsecurity_alpn)
|
resources.getStringArray(R.array.streamsecurity_alpn)
|
||||||
}
|
}
|
||||||
|
private val xhttpMode: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.xhttp_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach.
|
// Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach.
|
||||||
// We don't use AndroidViewBinding because, it is better to share similar logics for different
|
// We don't use AndroidViewBinding because, it is better to share similar logics for different
|
||||||
@@ -107,6 +113,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
||||||
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.lay_stream_alpn) }
|
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.lay_stream_alpn) }
|
||||||
private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) }
|
private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) }
|
||||||
|
private val et_preshared_key: EditText? by lazy { findViewById(R.id.et_preshared_key) }
|
||||||
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.lay_public_key) }
|
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.lay_public_key) }
|
||||||
private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) }
|
private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) }
|
||||||
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
|
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
|
||||||
@@ -119,6 +126,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val et_port_hop: EditText? by lazy { findViewById(R.id.et_port_hop) }
|
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_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) }
|
private val et_pinsha256: EditText? by lazy { findViewById(R.id.et_pinsha256) }
|
||||||
|
private val et_extra: EditText? by lazy { findViewById(R.id.et_extra) }
|
||||||
|
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -142,7 +151,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long,
|
||||||
) {
|
) {
|
||||||
val types = transportTypes(networks[position])
|
val types = transportTypes(networks[position])
|
||||||
sp_header_type?.isEnabled = types.size > 1
|
sp_header_type?.isEnabled = types.size > 1
|
||||||
@@ -150,28 +159,49 @@ class ServerActivity : BaseActivity() {
|
|||||||
ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
|
ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
sp_header_type?.adapter = adapter
|
sp_header_type?.adapter = adapter
|
||||||
sp_header_type_title?.text = if (networks[position] == "grpc")
|
sp_header_type_title?.text =
|
||||||
getString(R.string.server_lab_mode_type) else
|
when (networks[position]) {
|
||||||
getString(R.string.server_lab_head_type)
|
NetworkType.GRPC.type -> getString(R.string.server_lab_mode_type)
|
||||||
config?.headerType?.let { it ->
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> getString(R.string.server_lab_xhttp_mode)
|
||||||
sp_header_type?.setSelection(Utils.arrayFind(types, it))
|
else -> getString(R.string.server_lab_head_type)
|
||||||
}
|
}.orEmpty()
|
||||||
config?.host?.let { it ->
|
sp_header_type?.setSelection(
|
||||||
et_request_host?.text = Utils.getEditable(it)
|
Utils.arrayFind(
|
||||||
}
|
types,
|
||||||
config?.path?.let { it ->
|
when (networks[position]) {
|
||||||
et_path?.text = Utils.getEditable(it)
|
NetworkType.GRPC.type -> config?.mode
|
||||||
}
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> config?.xhttpMode
|
||||||
|
else -> config?.headerType
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
et_request_host?.text = Utils.getEditable(
|
||||||
|
when (networks[position]) {
|
||||||
|
//"quic" -> config?.quicSecurity
|
||||||
|
NetworkType.GRPC.type -> config?.authority
|
||||||
|
else -> config?.host
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
et_path?.text = Utils.getEditable(
|
||||||
|
when (networks[position]) {
|
||||||
|
NetworkType.KCP.type -> config?.seed
|
||||||
|
//"quic" -> config?.quicKey
|
||||||
|
NetworkType.GRPC.type -> config?.serviceName
|
||||||
|
else -> config?.path
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
tv_request_host?.text = Utils.getEditable(
|
tv_request_host?.text = Utils.getEditable(
|
||||||
getString(
|
getString(
|
||||||
when (networks[position]) {
|
when (networks[position]) {
|
||||||
"tcp" -> R.string.server_lab_request_host_http
|
NetworkType.TCP.type -> R.string.server_lab_request_host_http
|
||||||
"ws" -> R.string.server_lab_request_host_ws
|
NetworkType.WS.type -> R.string.server_lab_request_host_ws
|
||||||
"httpupgrade" -> R.string.server_lab_request_host_httpupgrade
|
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_request_host_httpupgrade
|
||||||
"splithttp" -> R.string.server_lab_request_host_splithttp
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> R.string.server_lab_request_host_xhttp
|
||||||
"h2" -> R.string.server_lab_request_host_h2
|
NetworkType.H2.type -> R.string.server_lab_request_host_h2
|
||||||
"quic" -> R.string.server_lab_request_host_quic
|
//"quic" -> R.string.server_lab_request_host_quic
|
||||||
"grpc" -> R.string.server_lab_request_host_grpc
|
NetworkType.GRPC.type -> R.string.server_lab_request_host_grpc
|
||||||
else -> R.string.server_lab_request_host
|
else -> R.string.server_lab_request_host
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -180,17 +210,29 @@ class ServerActivity : BaseActivity() {
|
|||||||
tv_path?.text = Utils.getEditable(
|
tv_path?.text = Utils.getEditable(
|
||||||
getString(
|
getString(
|
||||||
when (networks[position]) {
|
when (networks[position]) {
|
||||||
"kcp" -> R.string.server_lab_path_kcp
|
NetworkType.KCP.type -> R.string.server_lab_path_kcp
|
||||||
"ws" -> R.string.server_lab_path_ws
|
NetworkType.WS.type -> R.string.server_lab_path_ws
|
||||||
"httpupgrade" -> R.string.server_lab_path_httpupgrade
|
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_path_httpupgrade
|
||||||
"splithttp" -> R.string.server_lab_path_splithttp
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> R.string.server_lab_path_xhttp
|
||||||
"h2" -> R.string.server_lab_path_h2
|
NetworkType.H2.type -> R.string.server_lab_path_h2
|
||||||
"quic" -> R.string.server_lab_path_quic
|
//"quic" -> R.string.server_lab_path_quic
|
||||||
"grpc" -> R.string.server_lab_path_grpc
|
NetworkType.GRPC.type -> R.string.server_lab_path_grpc
|
||||||
else -> R.string.server_lab_path
|
else -> R.string.server_lab_path
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
et_extra?.text = Utils.getEditable(
|
||||||
|
when (networks[position]) {
|
||||||
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> config?.xhttpExtra
|
||||||
|
else -> null
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
layout_extra?.visibility =
|
||||||
|
when (networks[position]) {
|
||||||
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> View.VISIBLE
|
||||||
|
else -> View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
@@ -202,7 +244,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long,
|
||||||
) {
|
) {
|
||||||
val isBlank = streamSecuritys[position].isBlank()
|
val isBlank = streamSecuritys[position].isBlank()
|
||||||
val isTLS = streamSecuritys[position] == TLS
|
val isTLS = streamSecuritys[position] == TLS
|
||||||
@@ -280,29 +322,21 @@ class ServerActivity : BaseActivity() {
|
|||||||
} else if (config.configType == EConfigType.WIREGUARD) {
|
} else if (config.configType == EConfigType.WIREGUARD) {
|
||||||
et_id.text = Utils.getEditable(config.secretKey.orEmpty())
|
et_id.text = Utils.getEditable(config.secretKey.orEmpty())
|
||||||
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
||||||
if (config.reserved == null) {
|
et_preshared_key?.visibility = View.VISIBLE
|
||||||
et_reserved1?.text = Utils.getEditable("0,0,0")
|
et_preshared_key?.text = Utils.getEditable(config.preSharedKey.orEmpty())
|
||||||
} else {
|
et_reserved1?.text = Utils.getEditable(config.reserved ?: "0,0,0")
|
||||||
et_reserved1?.text = Utils.getEditable(config.reserved?.toString())
|
et_local_address?.text = Utils.getEditable(
|
||||||
}
|
config.localAddress ?: "$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}")
|
et_local_mtu?.text = Utils.getEditable(config.mtu?.toString() ?: WIREGUARD_LOCAL_MTU)
|
||||||
} else {
|
|
||||||
et_local_address?.text = Utils.getEditable(config.localAddress)
|
|
||||||
}
|
|
||||||
if (config.mtu == null) {
|
|
||||||
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
|
||||||
} else {
|
|
||||||
et_local_mtu?.text = Utils.getEditable(config.mtu.toString())
|
|
||||||
}
|
|
||||||
} else if (config.configType == EConfigType.HYSTERIA2) {
|
} else if (config.configType == EConfigType.HYSTERIA2) {
|
||||||
et_obfs_password?.text = Utils.getEditable(config.obfsPassword)
|
et_obfs_password?.text = Utils.getEditable(config.obfsPassword)
|
||||||
et_port_hop?.text = Utils.getEditable(config.portHopping)
|
et_port_hop?.text = Utils.getEditable(config.portHopping)
|
||||||
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
|
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
|
||||||
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
|
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
|
||||||
}
|
}
|
||||||
|
val securityEncryptions =
|
||||||
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||||
val security = Utils.arrayFind(securityEncryptions, config.method.orEmpty())
|
val security = Utils.arrayFind(securityEncryptions, config.method.orEmpty())
|
||||||
if (security >= 0) {
|
if (security >= 0) {
|
||||||
sp_security?.setSelection(security)
|
sp_security?.setSelection(security)
|
||||||
@@ -333,7 +367,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
container_public_key?.visibility = View.GONE
|
container_public_key?.visibility = View.GONE
|
||||||
container_short_id?.visibility = View.GONE
|
container_short_id?.visibility = View.GONE
|
||||||
container_spider_x?.visibility = View.GONE
|
container_spider_x?.visibility = View.GONE
|
||||||
} else if (config.security == REALITY) { // reality settings
|
} else if (config.security == REALITY) {
|
||||||
container_public_key?.visibility = View.VISIBLE
|
container_public_key?.visibility = View.VISIBLE
|
||||||
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
||||||
container_short_id?.visibility = View.VISIBLE
|
container_short_id?.visibility = View.VISIBLE
|
||||||
@@ -353,7 +387,6 @@ class ServerActivity : BaseActivity() {
|
|||||||
container_short_id?.visibility = View.GONE
|
container_short_id?.visibility = View.GONE
|
||||||
container_spider_x?.visibility = View.GONE
|
container_spider_x?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
val network = Utils.arrayFind(networks, config.network.orEmpty())
|
val network = Utils.arrayFind(networks, config.network.orEmpty())
|
||||||
if (network >= 0) {
|
if (network >= 0) {
|
||||||
sp_network?.setSelection(network)
|
sp_network?.setSelection(network)
|
||||||
@@ -407,7 +440,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(createConfigType)
|
val config =
|
||||||
|
MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(createConfigType)
|
||||||
if (config.configType != EConfigType.SOCKS
|
if (config.configType != EConfigType.SOCKS
|
||||||
&& config.configType != EConfigType.HTTP
|
&& config.configType != EConfigType.HTTP
|
||||||
&& TextUtils.isEmpty(et_id.text.toString())
|
&& TextUtils.isEmpty(et_id.text.toString())
|
||||||
@@ -428,6 +462,12 @@ class ServerActivity : BaseActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (et_extra?.text?.toString().isNotNullEmpty()) {
|
||||||
|
if (JsonUtil.parseString(et_extra?.text?.toString()) == null) {
|
||||||
|
toast(R.string.server_lab_xhttp_extra)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveCommon(config)
|
saveCommon(config)
|
||||||
saveStreamSettings(config)
|
saveStreamSettings(config)
|
||||||
@@ -436,7 +476,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
config.subscriptionId = subscriptionId.orEmpty()
|
config.subscriptionId = subscriptionId.orEmpty()
|
||||||
}
|
}
|
||||||
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config))
|
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config) ?: "")
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
finish()
|
finish()
|
||||||
@@ -464,6 +504,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
} else if (config.configType == EConfigType.WIREGUARD) {
|
} else if (config.configType == EConfigType.WIREGUARD) {
|
||||||
config.secretKey = et_id.text.toString().trim()
|
config.secretKey = et_id.text.toString().trim()
|
||||||
config.publicKey = et_public_key?.text.toString().trim()
|
config.publicKey = et_public_key?.text.toString().trim()
|
||||||
|
config.preSharedKey = et_preshared_key?.text.toString().trim()
|
||||||
config.reserved = et_reserved1?.text.toString().trim()
|
config.reserved = et_reserved1?.text.toString().trim()
|
||||||
config.localAddress = et_local_address?.text.toString().trim()
|
config.localAddress = et_local_address?.text.toString().trim()
|
||||||
config.mtu = Utils.parseInt(et_local_mtu?.text.toString())
|
config.mtu = Utils.parseInt(et_local_mtu?.text.toString())
|
||||||
@@ -492,6 +533,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
profileItem.mode = transportTypes(networks[network])[type]
|
profileItem.mode = transportTypes(networks[network])[type]
|
||||||
profileItem.serviceName = path
|
profileItem.serviceName = path
|
||||||
profileItem.authority = requestHost
|
profileItem.authority = requestHost
|
||||||
|
profileItem.xhttpMode = transportTypes(networks[network])[type]
|
||||||
|
profileItem.xhttpExtra = et_extra?.text?.toString()?.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveTls(config: ProfileItem) {
|
private fun saveTls(config: ProfileItem) {
|
||||||
@@ -506,7 +549,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
|
|
||||||
val allowInsecure =
|
val allowInsecure =
|
||||||
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
|
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
|
||||||
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE) == true
|
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE)
|
||||||
} else {
|
} else {
|
||||||
allowinsecures[allowInsecureField].toBoolean()
|
allowinsecures[allowInsecureField].toBoolean()
|
||||||
}
|
}
|
||||||
@@ -523,18 +566,22 @@ class ServerActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun transportTypes(network: String?): Array<out String> {
|
private fun transportTypes(network: String?): Array<out String> {
|
||||||
return when (network) {
|
return when (network) {
|
||||||
"tcp" -> {
|
NetworkType.TCP.type -> {
|
||||||
tcpTypes
|
tcpTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp", "quic" -> {
|
NetworkType.KCP.type -> {
|
||||||
kcpAndQuicTypes
|
kcpAndQuicTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
"grpc" -> {
|
NetworkType.GRPC.type -> {
|
||||||
grpcModes
|
grpcModes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
|
||||||
|
xhttpMode
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
arrayOf("---")
|
arrayOf("---")
|
||||||
}
|
}
|
||||||
+8
-5
@@ -12,10 +12,9 @@ import com.v2ray.ang.R
|
|||||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.fmt.CustomFmt
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import me.drakeet.support.toast.ToastCompat
|
import me.drakeet.support.toast.ToastCompat
|
||||||
|
|
||||||
@@ -75,8 +74,8 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val v2rayConfig = try {
|
val profileItem = try {
|
||||||
JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
|
CustomFmt.parse(binding.editor.text.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
@@ -84,7 +83,11 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.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()
|
binding.etRemarks.text.let {
|
||||||
|
config.remarks = if (it.isNullOrEmpty()) profileItem?.remarks.orEmpty() else it.toString()
|
||||||
|
}
|
||||||
|
config.server = profileItem?.server
|
||||||
|
config.serverPort = profileItem?.serverPort
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())
|
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())
|
||||||
+35
@@ -75,6 +75,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||||
R.id.add_file -> showFileChooser().let { true }
|
R.id.add_file -> showFileChooser().let { true }
|
||||||
R.id.add_url -> startActivity(Intent(this, UserAssetUrlActivity::class.java)).let { true }
|
R.id.add_url -> startActivity(Intent(this, UserAssetUrlActivity::class.java)).let { true }
|
||||||
|
R.id.add_qrcode -> importAssetFromQRcode().let { true }
|
||||||
R.id.download_file -> downloadGeoFiles().let { true }
|
R.id.download_file -> downloadGeoFiles().let { true }
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
@@ -156,6 +157,40 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun importAssetFromQRcode(): Boolean {
|
||||||
|
RxPermissions(this)
|
||||||
|
.request(Manifest.permission.CAMERA)
|
||||||
|
.subscribe {
|
||||||
|
if (it)
|
||||||
|
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
else
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scanQRCodeForAssetURL = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == RESULT_OK) {
|
||||||
|
importAsset(it.data?.getStringExtra("SCAN_RESULT"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importAsset(url: String?): Boolean {
|
||||||
|
try {
|
||||||
|
if (!Utils.isValidUrl(url)) {
|
||||||
|
toast(R.string.toast_invalid_url)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Send URL to UserAssetUrlActivity for Processing
|
||||||
|
startActivity(Intent(this, UserAssetUrlActivity::class.java)
|
||||||
|
.putExtra(UserAssetUrlActivity.ASSET_URL_QRCODE, url))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun downloadGeoFiles() {
|
private fun downloadGeoFiles() {
|
||||||
val dialog = AlertDialog.Builder(this)
|
val dialog = AlertDialog.Builder(this)
|
||||||
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
+14
-4
@@ -14,6 +14,11 @@ import com.v2ray.ang.util.Utils
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class UserAssetUrlActivity : BaseActivity() {
|
class UserAssetUrlActivity : BaseActivity() {
|
||||||
|
// Receive QRcode URL from UserAssetActivity
|
||||||
|
companion object {
|
||||||
|
const val ASSET_URL_QRCODE = "ASSET_URL_QRCODE"
|
||||||
|
}
|
||||||
|
|
||||||
private val binding by lazy { ActivityUserAssetUrlBinding.inflate(layoutInflater) }
|
private val binding by lazy { ActivityUserAssetUrlBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
var del_config: MenuItem? = null
|
||||||
@@ -28,10 +33,15 @@ class UserAssetUrlActivity : BaseActivity() {
|
|||||||
title = getString(R.string.title_user_asset_add_url)
|
title = getString(R.string.title_user_asset_add_url)
|
||||||
|
|
||||||
val assetItem = MmkvManager.decodeAsset(editAssetId)
|
val assetItem = MmkvManager.decodeAsset(editAssetId)
|
||||||
if (assetItem != null) {
|
val assetUrlQrcode = intent.getStringExtra(ASSET_URL_QRCODE)
|
||||||
bindingAsset(assetItem)
|
val assetNameQrcode = File(assetUrlQrcode.toString()).name
|
||||||
} else {
|
when {
|
||||||
clearAsset()
|
assetItem != null -> bindingAsset(assetItem)
|
||||||
|
assetUrlQrcode != null -> {
|
||||||
|
binding.etRemarks.setText(assetNameQrcode)
|
||||||
|
binding.etUrl.setText(assetUrlQrcode)
|
||||||
|
}
|
||||||
|
else -> clearAsset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+3
-5
@@ -13,13 +13,11 @@ object AppManagerUtil {
|
|||||||
val apps = ArrayList<AppInfo>()
|
val apps = ArrayList<AppInfo>()
|
||||||
|
|
||||||
for (pkg in packages) {
|
for (pkg in packages) {
|
||||||
//if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
|
val applicationInfo = pkg.applicationInfo ?: continue
|
||||||
|
|
||||||
val applicationInfo = pkg.applicationInfo
|
|
||||||
|
|
||||||
val appName = applicationInfo.loadLabel(packageManager).toString()
|
val appName = applicationInfo.loadLabel(packageManager).toString()
|
||||||
val appIcon = applicationInfo.loadIcon(packageManager)
|
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
|
||||||
val isSystemApp = applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM > 0
|
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
|
||||||
|
|
||||||
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
|
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
|
||||||
apps.add(appInfo)
|
apps.add(appInfo)
|
||||||
+18
-3
@@ -2,6 +2,8 @@ package com.v2ray.ang.util
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.JsonSerializationContext
|
import com.google.gson.JsonSerializationContext
|
||||||
import com.google.gson.JsonSerializer
|
import com.google.gson.JsonSerializer
|
||||||
@@ -15,11 +17,13 @@ object JsonUtil {
|
|||||||
return gson.toJson(src)
|
return gson.toJson(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> fromJson(json: String, cls: Class<T>): T {
|
fun <T> fromJson(src: String, cls: Class<T>): T {
|
||||||
return gson.fromJson(json, cls)
|
return gson.fromJson(src, cls)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJsonPretty(src: Any?): String {
|
fun toJsonPretty(src: Any?): String? {
|
||||||
|
if (src == null)
|
||||||
|
return null
|
||||||
val gsonPre = GsonBuilder()
|
val gsonPre = GsonBuilder()
|
||||||
.setPrettyPrinting()
|
.setPrettyPrinting()
|
||||||
.disableHtmlEscaping()
|
.disableHtmlEscaping()
|
||||||
@@ -34,4 +38,15 @@ object JsonUtil {
|
|||||||
.create()
|
.create()
|
||||||
return gsonPre.toJson(src)
|
return gsonPre.toJson(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseString(src: String?): JsonObject? {
|
||||||
|
if (src == null)
|
||||||
|
return null
|
||||||
|
try {
|
||||||
|
return JsonParser.parseString(src).getAsJsonObject()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+27
-8
@@ -17,6 +17,7 @@ import android.util.Log
|
|||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||||
@@ -130,7 +131,8 @@ object Utils {
|
|||||||
* get remote dns servers from preference
|
* get remote dns servers from preference
|
||||||
*/
|
*/
|
||||||
fun getRemoteDnsServers(): List<String> {
|
fun getRemoteDnsServers(): List<String> {
|
||||||
val remoteDns = MmkvManager.decodeSettingsString(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) }
|
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_PROXY)
|
return listOf(AppConfig.DNS_PROXY)
|
||||||
@@ -148,7 +150,8 @@ object Utils {
|
|||||||
* get remote dns servers from preference
|
* get remote dns servers from preference
|
||||||
*/
|
*/
|
||||||
fun getDomesticDnsServers(): List<String> {
|
fun getDomesticDnsServers(): List<String> {
|
||||||
val domesticDns = MmkvManager.decodeSettingsString(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) }
|
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_DIRECT)
|
return listOf(AppConfig.DNS_DIRECT)
|
||||||
@@ -159,8 +162,11 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* is ip address
|
* is ip address
|
||||||
*/
|
*/
|
||||||
fun isIpAddress(value: String): Boolean {
|
fun isIpAddress(value: String?): Boolean {
|
||||||
try {
|
try {
|
||||||
|
if (value.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
var addr = value
|
var addr = value
|
||||||
if (addr.isEmpty() || addr.isBlank()) {
|
if (addr.isEmpty() || addr.isBlank()) {
|
||||||
return false
|
return false
|
||||||
@@ -289,7 +295,7 @@ object Utils {
|
|||||||
|
|
||||||
fun urlEncode(url: String): String {
|
fun urlEncode(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
URLEncoder.encode(url, Charsets.UTF_8.toString())
|
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
url
|
url
|
||||||
@@ -353,7 +359,11 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getUrlContentWithCustomUserAgent(urlStr: String?, timeout: Int = 30000, httpPort: Int = 0): String {
|
fun getUrlContentWithCustomUserAgent(
|
||||||
|
urlStr: String?,
|
||||||
|
timeout: Int = 30000,
|
||||||
|
httpPort: Int = 0
|
||||||
|
): String {
|
||||||
val url = URL(urlStr)
|
val url = URL(urlStr)
|
||||||
val conn = if (httpPort == 0) {
|
val conn = if (httpPort == 0) {
|
||||||
url.openConnection()
|
url.openConnection()
|
||||||
@@ -386,7 +396,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun setNightMode(context: Context) {
|
fun setNightMode() {
|
||||||
when (MmkvManager.decodeSettingsString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
|
when (MmkvManager.decodeSettingsString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
|
||||||
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||||
@@ -406,7 +416,8 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getLocale(): Locale {
|
fun getLocale(): Locale {
|
||||||
val langCode = MmkvManager.decodeSettingsString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
|
val langCode =
|
||||||
|
MmkvManager.decodeSettingsString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
|
||||||
val language = Language.fromCode(langCode)
|
val language = Language.fromCode(langCode)
|
||||||
|
|
||||||
return when (language) {
|
return when (language) {
|
||||||
@@ -418,6 +429,7 @@ object Utils {
|
|||||||
Language.RUSSIAN -> Locale("ru")
|
Language.RUSSIAN -> Locale("ru")
|
||||||
Language.PERSIAN -> Locale("fa")
|
Language.PERSIAN -> Locale("fa")
|
||||||
Language.BANGLA -> Locale("bn")
|
Language.BANGLA -> Locale("bn")
|
||||||
|
Language.BAKHTIARI -> Locale("bqi", "IR")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +463,8 @@ object Utils {
|
|||||||
return if (second) {
|
return if (second) {
|
||||||
AppConfig.DelayTestUrl2
|
AppConfig.DelayTestUrl2
|
||||||
} else {
|
} else {
|
||||||
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
|
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL)
|
||||||
|
?: AppConfig.DelayTestUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,5 +492,11 @@ object Utils {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun receiverFlags(): Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
} else {
|
||||||
|
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+1
-1
@@ -31,7 +31,7 @@ object ZipUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (filesToCompress.isEmpty) {
|
if (filesToCompress.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
+11
-14
@@ -8,6 +8,7 @@ import android.content.IntentFilter
|
|||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -25,6 +26,7 @@ import com.v2ray.ang.handler.MmkvManager
|
|||||||
import com.v2ray.ang.handler.SettingsManager
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
import com.v2ray.ang.util.MessageUtil
|
import com.v2ray.ang.util.MessageUtil
|
||||||
import com.v2ray.ang.util.SpeedtestUtil
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -44,20 +46,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||||
|
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
|
||||||
|
*/
|
||||||
|
|
||||||
fun startListenBroadcast() {
|
fun startListenBroadcast() {
|
||||||
isRunning.value = false
|
isRunning.value = false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
||||||
getApplication<AngApplication>().registerReceiver(
|
ContextCompat.registerReceiver(getApplication(), mMsgReceiver, mFilter, Utils.receiverFlags())
|
||||||
mMsgReceiver,
|
|
||||||
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY),
|
|
||||||
Context.RECEIVER_EXPORTED
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
getApplication<AngApplication>().registerReceiver(
|
|
||||||
mMsgReceiver,
|
|
||||||
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
|
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,10 +257,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
val deleteServer = mutableListOf<String>()
|
val deleteServer = mutableListOf<String>()
|
||||||
serversCacheCopy.forEachIndexed { index, it ->
|
serversCacheCopy.forEachIndexed { index, it ->
|
||||||
val outbound = it.second.getKeyProperty()
|
val outbound = it.second
|
||||||
serversCacheCopy.forEachIndexed { index2, it2 ->
|
serversCacheCopy.forEachIndexed { index2, it2 ->
|
||||||
if (index2 > index) {
|
if (index2 > index) {
|
||||||
val outbound2 = it2.second.getKeyProperty()
|
val outbound2 = it2.second
|
||||||
if (outbound.equals(outbound2) && !deleteServer.contains(it2.first)) {
|
if (outbound.equals(outbound2) && !deleteServer.contains(it2.first)) {
|
||||||
deleteServer.add(it2.first)
|
deleteServer.add(it2.first)
|
||||||
}
|
}
|
||||||
+1
-1
@@ -81,7 +81,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
if (key == AppConfig.PREF_UI_MODE_NIGHT) {
|
if (key == AppConfig.PREF_UI_MODE_NIGHT) {
|
||||||
Utils.setNightMode(getApplication())
|
Utils.setNightMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user