Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 496a0483d2 | |||
| e11dca00bb | |||
| fde39bf34e | |||
| 4f11bae238 | |||
| f6282ba71f | |||
| 3edf1f4e1b | |||
| 41bc064083 | |||
| eb8562e6b0 | |||
| 68fbdd92c3 | |||
| 02038a5d93 | |||
| 4fb8c2f4b2 | |||
| 7afffa60c3 | |||
| 0e6c860360 | |||
| ebfbbfa08b | |||
| b5f182dfec | |||
| 4cf28d0ad0 | |||
| 149bb049a5 | |||
| 124702f0a2 | |||
| c1cebe578b | |||
| e46c1ee849 | |||
| 70f1743114 | |||
| 54d520727e | |||
| 1f1e4db486 | |||
| e536236634 | |||
| 140c236da5 | |||
| 69ede34274 | |||
| fcf6e22132 | |||
| 7438ee8308 | |||
| f01cf7fcb5 | |||
| 7a852f78e4 | |||
| 0923659a49 | |||
| f9feb08607 | |||
| 3b43fe39e5 | |||
| de30fa15b3 | |||
| 25a4d7c14d | |||
| c8d3607efe | |||
| a0e73a9aa9 | |||
| eaccf237a4 | |||
| 5124266346 | |||
| 5cf2ea5a1e | |||
| 7a1af5914e | |||
| e61f5eeb76 | |||
| 6d92106f9d | |||
| 8b06745e86 | |||
| 85ad999975 | |||
| 4c0f2d84cc | |||
| 6fc9803431 | |||
| 59a710bae5 | |||
| 98c642e1a8 | |||
| e91f4470fb | |||
| b33cc5284f | |||
| eea6db6814 | |||
| ba622c7edf | |||
| b52e89d614 | |||
| 49cd3a0494 | |||
| 75cc16c5e0 | |||
| e674d22ecd | |||
| dca5011eb1 | |||
| 4d8e38b704 | |||
| 5ba4352641 | |||
| 1b2cc11a97 | |||
| a3591e4bbb | |||
| aa0f5639b1 | |||
| f3abd0d9fc | |||
| 4a62aff7d2 | |||
| c78e624eaf | |||
| 934cf5d21c | |||
| 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 |
+108
-32
@@ -1,13 +1,14 @@
|
|||||||
name: Build APK
|
name: Build APK
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
XRAY_CORE_VERSION:
|
release_tag:
|
||||||
description: 'Xray core version or commit hash'
|
|
||||||
required: false
|
required: false
|
||||||
|
type: string
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -17,77 +18,152 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Prepare build dir
|
||||||
uses: actions/setup-java@v4
|
run: |
|
||||||
|
mkdir ${{ github.workspace }}/build
|
||||||
|
|
||||||
|
- name: Fetch AndroidLibV2rayLite
|
||||||
|
run: |
|
||||||
|
cd ${{ github.workspace }}/build
|
||||||
|
git clone --depth=1 -b master https://github.com/2dust/AndroidLibV2rayLite.git
|
||||||
|
cd AndroidLibV2rayLite
|
||||||
|
git submodule update --init
|
||||||
|
|
||||||
|
- name: Restore cached libtun2socks
|
||||||
|
id: cache-libtun2socks-restore
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
path: ${{ github.workspace }}/build/AndroidLibV2rayLite/libs
|
||||||
java-version: '21'
|
key: libtun2socks-${{ runner.os }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/refs/heads/master') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/badvpn/HEAD') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/libancillary/HEAD') }}
|
||||||
|
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
id: setup-ndk
|
||||||
|
# Same version as https://gitlab.com/fdroid/fdroiddata/metadata/com.v2ray.ang.yml
|
||||||
|
with:
|
||||||
|
ndk-version: r27
|
||||||
|
|
||||||
|
- name: Build libtun2socks
|
||||||
|
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
cd ${{ github.workspace }}/build/AndroidLibV2rayLite
|
||||||
|
bash compile-tun2socks.sh
|
||||||
|
tar -xvzf libtun2socks.so.tgz
|
||||||
|
cp -r libs/* ${{ github.workspace }}/V2rayNG/app/libs/
|
||||||
|
env:
|
||||||
|
NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
|
||||||
|
- name: Save libtun2socks
|
||||||
|
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: ${{ github.workspace }}/build/AndroidLibV2rayLite/libs
|
||||||
|
key: libtun2socks-${{ runner.os }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/refs/heads/master') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/badvpn/HEAD') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/libancillary/HEAD') }}
|
||||||
|
|
||||||
|
- name: Copy libtun2socks
|
||||||
|
run: |
|
||||||
|
cp -r ${{ github.workspace }}/build/AndroidLibV2rayLite/libs/* ${{ github.workspace }}/V2rayNG/app/libs/
|
||||||
|
|
||||||
|
- name: Fetch AndroidLibXrayLite
|
||||||
|
run: |
|
||||||
|
cd ${{ github.workspace }}/build
|
||||||
|
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
|
||||||
|
|
||||||
|
- name: Restore cached libv2ray
|
||||||
|
id: cache-libv2ray-restore
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: ${{ github.workspace }}/build/AndroidLibXrayLite/
|
||||||
|
key: libv2ray-${{ runner.os }}-${{ hashFiles('build/AndroidLibXrayLite/.git/refs/heads/main') }}
|
||||||
|
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
|
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23.2'
|
go-version: '1.23.4'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Patch Go use 600296
|
- name: Patch Go use 600296
|
||||||
|
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
||||||
#https://go-review.googlesource.com/c/go/+/600296
|
#https://go-review.googlesource.com/c/go/+/600296
|
||||||
run: |
|
run: |
|
||||||
cd "$(go env GOROOT)"
|
cd "$(go env GOROOT)"
|
||||||
curl "https://go-review.googlesource.com/changes/go~600296/revisions/5/patch" | base64 -d | patch --verbose -p 1
|
curl "https://go-review.googlesource.com/changes/go~600296/revisions/5/patch" | base64 -d | patch --verbose -p 1
|
||||||
|
|
||||||
- name: Install gomobile
|
- name: Install gomobile
|
||||||
|
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240806205939-81131f6468ab
|
go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240806205939-81131f6468ab
|
||||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Setup Android environment
|
- name: Build libv2ray
|
||||||
uses: android-actions/setup-android@v3
|
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
||||||
|
|
||||||
- name: Build dependencies
|
|
||||||
run: |
|
run: |
|
||||||
mkdir ${{ github.workspace }}/build
|
cd ${{ github.workspace }}/build/AndroidLibXrayLite
|
||||||
cd ${{ github.workspace }}/build
|
|
||||||
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
|
|
||||||
cd AndroidLibXrayLite
|
|
||||||
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
|
|
||||||
gomobile init
|
gomobile init
|
||||||
go mod tidy -v
|
go mod tidy -v
|
||||||
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
|
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
|
||||||
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
|
||||||
|
- name: Save libv2ray
|
||||||
|
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: ${{ github.workspace }}/build/AndroidLibXrayLite/
|
||||||
|
key: libv2ray-${{ runner.os }}-${{ hashFiles('build/AndroidLibXrayLite/.git/refs/heads/main') }}
|
||||||
|
|
||||||
|
- name: Copy libv2ray
|
||||||
|
run: |
|
||||||
|
cp -r ${{ github.workspace }}/build/AndroidLibXrayLite/*.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Setup Android environment
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Decode Keystore
|
||||||
|
uses: timheuer/base64-to-file@v1
|
||||||
|
id: android_keystore
|
||||||
|
with:
|
||||||
|
fileName: "android_keystore.jks"
|
||||||
|
encodedString: ${{ secrets.APP_KEYSTORE_BASE64 }}
|
||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
run: |
|
run: |
|
||||||
cd ${{ github.workspace }}/V2rayNG
|
cd ${{ github.workspace }}/V2rayNG
|
||||||
chmod 755 gradlew
|
chmod 755 gradlew
|
||||||
./gradlew assembleDebug
|
./gradlew licenseFdroidReleaseReport
|
||||||
|
./gradlew assembleRelease -Pandroid.injected.signing.store.file=${{ steps.android_keystore.outputs.filePath }} -Pandroid.injected.signing.store.password=${{ secrets.APP_KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.APP_KEYSTORE_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.APP_KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Upload arm64-v8a APK
|
- name: Upload arm64-v8a APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
with:
|
with:
|
||||||
name: arm64-v8a
|
name: arm64-v8a
|
||||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*arm64-v8a*.apk
|
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*arm64-v8a*.apk
|
||||||
|
|
||||||
- name: Upload armeabi-v7a APK
|
- name: Upload armeabi-v7a APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
with:
|
with:
|
||||||
name: armeabi-v7a
|
name: armeabi-v7a
|
||||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*armeabi-v7a*.apk
|
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*armeabi-v7a*.apk
|
||||||
|
|
||||||
- name: Upload x86 APK
|
- name: Upload x86 APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
with:
|
with:
|
||||||
name: x86-apk
|
name: x86-apk
|
||||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*x86*.apk
|
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*x86*.apk
|
||||||
|
|
||||||
- name: Upload Other APKs
|
- name: Upload AndroidLibXrayLite to release
|
||||||
uses: actions/upload-artifact@v4
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
with:
|
with:
|
||||||
name: others-apk
|
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*playstore*/release/*.apk
|
||||||
path: |
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug
|
file_glob: true
|
||||||
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*arm64-v8a*.apk
|
|
||||||
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*armeabi-v7a*.apk
|
|
||||||
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*x86*.apk
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
name: Validate Fastlane metadata
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
go:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Validate Fastlane Supply Metadata
|
||||||
|
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.0.0
|
||||||
@@ -3,3 +3,5 @@
|
|||||||
V2rayNG/app/release/output.json
|
V2rayNG/app/release/output.json
|
||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
|
libtun2socks.so
|
||||||
|
libhysteria2.so
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
[](https://developer.android.com/about/versions/lollipop)
|
[](https://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
|
|||||||
+124
-68
@@ -1,46 +1,63 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
alias(libs.plugins.android.application)
|
||||||
id("org.jetbrains.kotlin.android")
|
alias(libs.plugins.kotlin.android)
|
||||||
|
id("com.jaredsburrows.license")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = 607
|
versionCode = 623
|
||||||
versionName = "1.9.12"
|
versionName = "1.9.27"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
|
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
include(
|
reset()
|
||||||
"arm64-v8a",
|
if (abiFilterList != null && abiFilterList.isNotEmpty()) {
|
||||||
"armeabi-v7a",
|
include(*abiFilterList.toTypedArray())
|
||||||
"x86_64",
|
} else {
|
||||||
"x86"
|
include(
|
||||||
)
|
"arm64-v8a",
|
||||||
isUniversalApk = true
|
"armeabi-v7a",
|
||||||
|
"x86_64",
|
||||||
|
"x86"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isUniversalApk = abiFilterList.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
debug {
|
}
|
||||||
isMinifyEnabled = false
|
|
||||||
|
|
||||||
|
flavorDimensions.add("distribution")
|
||||||
|
productFlavors {
|
||||||
|
create("fdroid") {
|
||||||
|
dimension = "distribution"
|
||||||
|
applicationIdSuffix = ".fdroid"
|
||||||
|
buildConfigField("String", "DISTRIBUTION", "\"F-Droid\"")
|
||||||
|
}
|
||||||
|
create("playstore") {
|
||||||
|
dimension = "distribution"
|
||||||
|
buildConfigField("String", "DISTRIBUTION", "\"Play Store\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,30 +67,57 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
val variant = this
|
val variant = this
|
||||||
val versionCodes =
|
val isFdroid = variant.productFlavors.any { it.name == "fdroid" }
|
||||||
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
|
if (isFdroid) {
|
||||||
|
val versionCodes =
|
||||||
|
mapOf("armeabi-v7a" to 2, "arm64-v8a" to 1, "x86" to 4, "x86_64" to 3, "universal" to 0
|
||||||
|
)
|
||||||
|
|
||||||
variant.outputs
|
variant.outputs
|
||||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
.forEach { output ->
|
.forEach { output ->
|
||||||
val abi = if (output.getFilter("ABI") != null)
|
val abi = output.getFilter("ABI") ?: "universal"
|
||||||
output.getFilter("ABI")
|
output.outputFileName = "v2rayNG_${variant.versionName}-fdroid_${abi}.apk"
|
||||||
else
|
if (versionCodes.containsKey(abi)) {
|
||||||
"universal"
|
output.versionCodeOverride =
|
||||||
|
(100 * variant.versionCode + versionCodes[abi]!!).plus(5000000)
|
||||||
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
} else {
|
||||||
if (versionCodes.containsKey(abi)) {
|
return@forEach
|
||||||
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
}
|
||||||
} else {
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
val versionCodes =
|
||||||
|
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
|
||||||
|
|
||||||
|
variant.outputs
|
||||||
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
|
.forEach { output ->
|
||||||
|
val abi = if (output.getFilter("ABI") != null)
|
||||||
|
output.getFilter("ABI")
|
||||||
|
else
|
||||||
|
"universal"
|
||||||
|
|
||||||
|
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
||||||
|
if (versionCodes.containsKey(abi)) {
|
||||||
|
output.versionCodeOverride =
|
||||||
|
(1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
||||||
|
} else {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -86,49 +130,61 @@ 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.foss)
|
||||||
|
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)
|
||||||
|
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+3
-7
@@ -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,16 +38,11 @@ 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)
|
||||||
|
|
||||||
SettingsManager.initRoutingRulesets(this)
|
SettingsManager.initRoutingRulesets(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPackageInfo(packageName: String) = packageManager.getPackageInfo(
|
|
||||||
packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
|
|
||||||
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
|
|
||||||
)!!
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+11
-6
@@ -22,6 +22,7 @@ object AppConfig {
|
|||||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||||
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
||||||
|
const val PREF_APPEND_HTTP_PROXY = "pref_append_http_proxy"
|
||||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
@@ -47,9 +48,9 @@ object AppConfig {
|
|||||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||||
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||||
const val PREF_SOCKS_PORT = "pref_socks_port"
|
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||||
const val PREF_HTTP_PORT = "pref_http_port"
|
|
||||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||||
|
const val PREF_DNS_HOSTS = "pref_dns_hosts"
|
||||||
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
||||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||||
const val PREF_MODE = "pref_mode"
|
const val PREF_MODE = "pref_mode"
|
||||||
@@ -111,7 +112,6 @@ object AppConfig {
|
|||||||
/** Ports and addresses for various services. */
|
/** Ports and addresses for various services. */
|
||||||
const val PORT_LOCAL_DNS = "10853"
|
const val PORT_LOCAL_DNS = "10853"
|
||||||
const val PORT_SOCKS = "10808"
|
const val PORT_SOCKS = "10808"
|
||||||
const val PORT_HTTP = "10809"
|
|
||||||
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
|
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
|
||||||
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
||||||
const val WIREGUARD_LOCAL_MTU = "1420"
|
const val WIREGUARD_LOCAL_MTU = "1420"
|
||||||
@@ -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 {
|
||||||
+2
-2
@@ -5,10 +5,10 @@ enum class NetworkType(val type: String) {
|
|||||||
KCP("kcp"),
|
KCP("kcp"),
|
||||||
WS("ws"),
|
WS("ws"),
|
||||||
HTTP_UPGRADE("httpupgrade"),
|
HTTP_UPGRADE("httpupgrade"),
|
||||||
SPLIT_HTTP("splithttp"),
|
XHTTP("xhttp"),
|
||||||
HTTP("http"),
|
HTTP("http"),
|
||||||
H2("h2"),
|
H2("h2"),
|
||||||
QUIC("quic"),
|
//QUIC("quic"),
|
||||||
GRPC("grpc");
|
GRPC("grpc");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
+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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -9,5 +9,5 @@ data class RulesetItem(
|
|||||||
var network: String? = null,
|
var network: String? = null,
|
||||||
var protocol: List<String>? = null,
|
var protocol: List<String>? = null,
|
||||||
var enabled: Boolean = true,
|
var enabled: Boolean = true,
|
||||||
var looked: Boolean? = false,
|
var locked: Boolean? = false,
|
||||||
)
|
)
|
||||||
+87
-72
@@ -195,18 +195,19 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
data class WireGuardBean(
|
data class WireGuardBean(
|
||||||
var publicKey: String = "",
|
var publicKey: String = "",
|
||||||
|
var preSharedKey: String = "",
|
||||||
var endpoint: String = ""
|
var endpoint: String = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StreamSettingsBean(
|
data class StreamSettingsBean(
|
||||||
var network: String = AppConfig.DEFAULT_NETWORK,
|
var network: String = AppConfig.DEFAULT_NETWORK,
|
||||||
var security: String = "",
|
var security: String? = null,
|
||||||
var tcpSettings: TcpSettingsBean? = null,
|
var tcpSettings: TcpSettingsBean? = null,
|
||||||
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 = if (transport.isEmpty()) NetworkType.TCP.type else 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
|
||||||
@@ -360,16 +368,16 @@ data class V2rayConfig(
|
|||||||
requestObj.headers.Host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.headers.Host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
requestObj.path = path.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.path = path.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
tcpSetting.header.request = requestObj
|
tcpSetting.header.request = requestObj
|
||||||
sni = requestObj.headers.Host?.getOrNull(0) ?: sni
|
sni = requestObj.headers.Host?.getOrNull(0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tcpSetting.header.type = "none"
|
tcpSetting.header.type = "none"
|
||||||
sni = host.orEmpty()
|
sni = host
|
||||||
}
|
}
|
||||||
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,55 +388,55 @@ 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 = host
|
||||||
wssetting.path = path ?: "/"
|
wssetting.path = path ?: "/"
|
||||||
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 = host
|
||||||
httpupgradeSetting.path = path ?: "/"
|
httpupgradeSetting.path = path ?: "/"
|
||||||
httpupgradeSettings = httpupgradeSetting
|
httpupgradeSettings = httpupgradeSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"splithttp" -> {
|
NetworkType.XHTTP.type -> {
|
||||||
val splithttpSetting = SplithttpSettingsBean()
|
val xhttpSetting = XhttpSettingsBean()
|
||||||
splithttpSetting.host = host.orEmpty()
|
xhttpSetting.host = host.orEmpty()
|
||||||
sni = splithttpSetting.host
|
sni = 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)
|
||||||
h2Setting.path = path ?: "/"
|
h2Setting.path = path ?: "/"
|
||||||
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()
|
||||||
grpcSetting.authority = authority.orEmpty()
|
grpcSetting.authority = authority.orEmpty()
|
||||||
grpcSetting.idle_timeout = 60
|
grpcSetting.idle_timeout = 60
|
||||||
grpcSetting.health_check_timeout = 20
|
grpcSetting.health_check_timeout = 20
|
||||||
sni = authority.orEmpty()
|
sni = authority
|
||||||
grpcSettings = grpcSetting
|
grpcSettings = grpcSetting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,18 +444,25 @@ 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 = if (streamSecurity.isEmpty()) null else streamSecurity
|
||||||
|
if (security == null) return
|
||||||
val tlsSetting = TlsSettingsBean(
|
val tlsSetting = TlsSettingsBean(
|
||||||
allowInsecure = allowInsecure,
|
allowInsecure = allowInsecure,
|
||||||
serverName = sni,
|
serverName = if (sni.isNullOrEmpty()) null else sni,
|
||||||
fingerprint = fingerprint,
|
fingerprint = if (fingerprint.isNullOrEmpty()) null else fingerprint,
|
||||||
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
|
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
|
||||||
publicKey = publicKey,
|
publicKey = if (publicKey.isNullOrEmpty()) null else publicKey,
|
||||||
shortId = shortId,
|
shortId = if (shortId.isNullOrEmpty()) null else shortId,
|
||||||
spiderX = spiderX
|
spiderX = if (spiderX.isNullOrEmpty()) null else spiderX,
|
||||||
)
|
)
|
||||||
if (security == AppConfig.TLS) {
|
if (security == AppConfig.TLS) {
|
||||||
tlsSettings = tlsSetting
|
tlsSettings = tlsSetting
|
||||||
@@ -470,16 +485,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 +503,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 +527,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 +545,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 +560,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 +569,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 +578,7 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws" -> {
|
NetworkType.WS.type -> {
|
||||||
val wsSetting = streamSettings?.wsSettings ?: return null
|
val wsSetting = streamSettings?.wsSettings ?: return null
|
||||||
listOf(
|
listOf(
|
||||||
"",
|
"",
|
||||||
@@ -572,7 +587,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 +596,16 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"splithttp" -> {
|
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 +614,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",
|
||||||
+1
-1
@@ -95,4 +95,4 @@ inline fun <reified T : Serializable> Intent.serializable(key: String): T? = whe
|
|||||||
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
|
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())
|
fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())
|
||||||
+46
-6
@@ -30,6 +30,39 @@ 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
|
||||||
|
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"]
|
||||||
|
if (config.security != AppConfig.TLS && config.security != AppConfig.REALITY) {
|
||||||
|
config.security = null
|
||||||
|
}
|
||||||
|
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()
|
||||||
@@ -55,22 +88,29 @@ open class FmtBase {
|
|||||||
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
|
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.WS, NetworkType.HTTP_UPGRADE, NetworkType.SPLIT_HTTP -> {
|
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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 -> {
|
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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.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() }
|
||||||
}
|
// }
|
||||||
|
|
||||||
NetworkType.GRPC -> {
|
NetworkType.GRPC -> {
|
||||||
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
|
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = 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,139 @@
|
|||||||
|
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"]?.contains("obfs=http") == true) {
|
||||||
|
val queryPairs = HashMap<String, String>()
|
||||||
|
for (pair in queryParam["plugin"]?.split(";") ?: listOf()) {
|
||||||
|
val idx = pair.split("=")
|
||||||
|
if (idx.count() == 2) {
|
||||||
|
queryPairs.put(idx.first(), idx.last())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.network = NetworkType.TCP.type
|
||||||
|
config.headerType = "http"
|
||||||
|
config.host = queryPairs["obfs-host"]
|
||||||
|
config.path = queryPairs["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
|
||||||
|
}
|
||||||
|
|
||||||
|
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
profileItem.network.orEmpty(),
|
||||||
|
profileItem.headerType,
|
||||||
|
profileItem.host,
|
||||||
|
profileItem.path,
|
||||||
|
profileItem.seed,
|
||||||
|
profileItem.quicSecurity,
|
||||||
|
profileItem.quicKey,
|
||||||
|
profileItem.mode,
|
||||||
|
profileItem.serviceName,
|
||||||
|
profileItem.authority,
|
||||||
|
)
|
||||||
|
|
||||||
|
outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
profileItem.security.orEmpty(),
|
||||||
|
profileItem.insecure == true,
|
||||||
|
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
||||||
|
profileItem.fingerPrint,
|
||||||
|
profileItem.alpn,
|
||||||
|
profileItem.publicKey,
|
||||||
|
profileItem.shortId,
|
||||||
|
profileItem.spiderX,
|
||||||
|
)
|
||||||
|
|
||||||
|
return outboundBean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+3
-2
@@ -15,6 +15,7 @@ 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
|
||||||
@@ -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()) {
|
||||||
+7
-29
@@ -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,14 +45,14 @@ 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
|
||||||
server.flow = profileItem.flow
|
server.flow = profileItem.flow
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
profileItem.network.orEmpty(),
|
profileItem.network.orEmpty(),
|
||||||
profileItem.headerType,
|
profileItem.headerType,
|
||||||
profileItem.host,
|
profileItem.host,
|
||||||
@@ -90,7 +68,7 @@ object TrojanFmt : FmtBase() {
|
|||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
profileItem.security.orEmpty(),
|
profileItem.security.orEmpty(),
|
||||||
profileItem.insecure == true,
|
profileItem.insecure == true,
|
||||||
profileItem.sni,
|
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
||||||
profileItem.fingerPrint,
|
profileItem.fingerPrint,
|
||||||
profileItem.alpn,
|
profileItem.alpn,
|
||||||
profileItem.publicKey,
|
profileItem.publicKey,
|
||||||
+7
-28
@@ -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()
|
||||||
@@ -73,7 +50,7 @@ object VlessFmt : FmtBase() {
|
|||||||
vnext.users[0].flow = profileItem.flow
|
vnext.users[0].flow = profileItem.flow
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
profileItem.network.orEmpty(),
|
profileItem.network.orEmpty(),
|
||||||
profileItem.headerType,
|
profileItem.headerType,
|
||||||
profileItem.host,
|
profileItem.host,
|
||||||
@@ -85,11 +62,13 @@ 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(),
|
||||||
profileItem.insecure == true,
|
profileItem.insecure == true,
|
||||||
profileItem.sni,
|
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
||||||
profileItem.fingerPrint,
|
profileItem.fingerPrint,
|
||||||
profileItem.alpn,
|
profileItem.alpn,
|
||||||
profileItem.publicKey,
|
profileItem.publicKey,
|
||||||
+13
-29
@@ -48,7 +48,7 @@ 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
|
||||||
@@ -58,10 +58,10 @@ object VmessFmt : FmtBase() {
|
|||||||
config.seed = vmessQRCode.path
|
config.seed = vmessQRCode.path
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.QUIC -> {
|
// NetworkType.QUIC -> {
|
||||||
config.quicSecurity = vmessQRCode.host
|
// config.quicSecurity = vmessQRCode.host
|
||||||
config.quicKey = vmessQRCode.path
|
// config.quicKey = vmessQRCode.path
|
||||||
}
|
// }
|
||||||
|
|
||||||
NetworkType.GRPC -> {
|
NetworkType.GRPC -> {
|
||||||
config.mode = vmessQRCode.type
|
config.mode = vmessQRCode.type
|
||||||
@@ -98,10 +98,10 @@ object VmessFmt : FmtBase() {
|
|||||||
vmessQRCode.path = config.seed.orEmpty()
|
vmessQRCode.path = config.seed.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.QUIC -> {
|
// NetworkType.QUIC -> {
|
||||||
vmessQRCode.host = config.quicSecurity.orEmpty()
|
// vmessQRCode.host = config.quicSecurity.orEmpty()
|
||||||
vmessQRCode.path = config.quicKey.orEmpty()
|
// vmessQRCode.path = config.quicKey.orEmpty()
|
||||||
}
|
// }
|
||||||
|
|
||||||
NetworkType.GRPC -> {
|
NetworkType.GRPC -> {
|
||||||
vmessQRCode.type = config.mode.orEmpty()
|
vmessQRCode.type = config.mode.orEmpty()
|
||||||
@@ -137,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 = NetworkType.fromString(queryParam["type"]).name
|
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
|
||||||
}
|
}
|
||||||
@@ -162,14 +146,14 @@ 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()
|
||||||
vnext.users[0].security = profileItem.method
|
vnext.users[0].security = profileItem.method
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
profileItem.network.orEmpty(),
|
profileItem.network.orEmpty(),
|
||||||
profileItem.headerType,
|
profileItem.headerType,
|
||||||
profileItem.host,
|
profileItem.host,
|
||||||
@@ -185,7 +169,7 @@ object VmessFmt : FmtBase() {
|
|||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
profileItem.security.orEmpty(),
|
profileItem.security.orEmpty(),
|
||||||
profileItem.insecure == true,
|
profileItem.insecure == true,
|
||||||
profileItem.sni,
|
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
||||||
profileItem.fingerPrint,
|
profileItem.fingerPrint,
|
||||||
profileItem.alpn,
|
profileItem.alpn,
|
||||||
null,
|
null,
|
||||||
+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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
+9
-2
@@ -138,11 +138,11 @@ object AngConfigManager {
|
|||||||
if (sb.count() > 0) {
|
if (sb.count() > 0) {
|
||||||
Utils.setClipboard(context, sb.toString())
|
Utils.setClipboard(context, sb.toString())
|
||||||
}
|
}
|
||||||
|
return sb.lines().count()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,6 +170,13 @@ object AngConfigManager {
|
|||||||
if (guid == null) return -1
|
if (guid == null) return -1
|
||||||
val result = V2rayConfigManager.getV2rayConfig(context, guid)
|
val result = V2rayConfigManager.getV2rayConfig(context, guid)
|
||||||
if (result.status) {
|
if (result.status) {
|
||||||
|
val config = MmkvManager.decodeServerConfig(guid)
|
||||||
|
if (config?.configType == EConfigType.HYSTERIA2) {
|
||||||
|
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
|
||||||
|
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort)
|
||||||
|
Utils.setClipboard(context, JsonUtil.toJsonPretty(hy2Config) + "\n" + result.content)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Utils.setClipboard(context, result.content)
|
Utils.setClipboard(context, result.content)
|
||||||
} else {
|
} else {
|
||||||
return -1
|
return -1
|
||||||
@@ -280,7 +287,7 @@ object AngConfigManager {
|
|||||||
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: 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
|
||||||
@@ -36,7 +37,7 @@ object MigrateManager {
|
|||||||
|
|
||||||
//check and remove old
|
//check and remove old
|
||||||
decodeServerConfig(guid) ?: continue
|
decodeServerConfig(guid) ?: continue
|
||||||
//serverStorage.remove(guid)
|
serverStorage.remove(guid)
|
||||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + config.remarks)
|
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + config.remarks)
|
||||||
}
|
}
|
||||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-end")
|
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-end")
|
||||||
@@ -70,9 +71,9 @@ 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?.get(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 ?: "tcp"
|
config.network = outbound?.streamSettings?.network ?: NetworkType.TCP.type
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
config.headerType = transportDetails[0].orEmpty()
|
config.headerType = transportDetails[0].orEmpty()
|
||||||
config.host = transportDetails[1].orEmpty()
|
config.host = transportDetails[1].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
|
||||||
+9
-3
@@ -164,18 +164,22 @@ object MmkvManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllServer() {
|
fun removeAllServer(): Int {
|
||||||
|
val count = profileFullStorage.allKeys()?.count() ?: 0
|
||||||
mainStorage.clearAll()
|
mainStorage.clearAll()
|
||||||
profileFullStorage.clearAll()
|
profileFullStorage.clearAll()
|
||||||
//profileStorage.clearAll()
|
//profileStorage.clearAll()
|
||||||
serverAffStorage.clearAll()
|
serverAffStorage.clearAll()
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeInvalidServer(guid: String) {
|
fun removeInvalidServer(guid: String): Int {
|
||||||
|
var count = 0
|
||||||
if (guid.isNotEmpty()) {
|
if (guid.isNotEmpty()) {
|
||||||
decodeServerAffiliationInfo(guid)?.let { aff ->
|
decodeServerAffiliationInfo(guid)?.let { aff ->
|
||||||
if (aff.testDelayMillis < 0L) {
|
if (aff.testDelayMillis < 0L) {
|
||||||
removeServer(guid)
|
removeServer(guid)
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -183,10 +187,12 @@ object MmkvManager {
|
|||||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
if (aff.testDelayMillis < 0L) {
|
if (aff.testDelayMillis < 0L) {
|
||||||
removeServer(key)
|
removeServer(key)
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encodeServerRaw(guid: String, config: String) {
|
fun encodeServerRaw(guid: String, config: String) {
|
||||||
@@ -336,7 +342,7 @@ object MmkvManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun decodeSettingsBool(key: String): Boolean {
|
fun decodeSettingsBool(key: String): Boolean {
|
||||||
return settingsStorage.decodeBool(key,false)
|
return settingsStorage.decodeBool(key, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {
|
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {
|
||||||
+5
-5
@@ -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
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ object SettingsManager {
|
|||||||
private fun resetRoutingRulesetsCommon(rulesetList: MutableList<RulesetItem>) {
|
private fun resetRoutingRulesetsCommon(rulesetList: MutableList<RulesetItem>) {
|
||||||
val rulesetNew: MutableList<RulesetItem> = mutableListOf()
|
val rulesetNew: MutableList<RulesetItem> = mutableListOf()
|
||||||
MmkvManager.decodeRoutingRulesets()?.forEach { key ->
|
MmkvManager.decodeRoutingRulesets()?.forEach { key ->
|
||||||
if (key.looked == true) {
|
if (key.locked == true) {
|
||||||
rulesetNew.add(key)
|
rulesetNew.add(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ object SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getHttpPort(): Int {
|
fun getHttpPort(): Int {
|
||||||
return parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
return getSocksPort() + (if (Utils.isXray()) 0 else 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initAssets(context: Context, assets: AssetManager) {
|
fun initAssets(context: Context, assets: AssetManager) {
|
||||||
+44
-22
@@ -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
|
||||||
@@ -30,10 +34,12 @@ import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
|||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||||
import com.v2ray.ang.dto.ConfigResult
|
import com.v2ray.ang.dto.ConfigResult
|
||||||
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.RulesetItem
|
import com.v2ray.ang.dto.RulesetItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
|
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
|
||||||
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.fmt.HttpFmt
|
import com.v2ray.ang.fmt.HttpFmt
|
||||||
import com.v2ray.ang.fmt.Hysteria2Fmt
|
import com.v2ray.ang.fmt.Hysteria2Fmt
|
||||||
import com.v2ray.ang.fmt.ShadowsocksFmt
|
import com.v2ray.ang.fmt.ShadowsocksFmt
|
||||||
@@ -57,7 +63,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) {
|
||||||
@@ -116,7 +122,6 @@ object V2rayConfigManager {
|
|||||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
val socksPort = SettingsManager.getSocksPort()
|
val socksPort = SettingsManager.getSocksPort()
|
||||||
val httpPort = SettingsManager.getHttpPort()
|
|
||||||
|
|
||||||
v2rayConfig.inbounds.forEach { curInbound ->
|
v2rayConfig.inbounds.forEach { curInbound ->
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
||||||
@@ -138,14 +143,13 @@ object V2rayConfigManager {
|
|||||||
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
|
||||||
}
|
}
|
||||||
|
|
||||||
v2rayConfig.inbounds[1].port = httpPort
|
if (Utils.isXray()) {
|
||||||
|
v2rayConfig.inbounds.removeAt(1)
|
||||||
|
} else {
|
||||||
|
val httpPort = SettingsManager.getHttpPort()
|
||||||
|
v2rayConfig.inbounds[1].port = httpPort
|
||||||
|
}
|
||||||
|
|
||||||
// if (httpPort > 0) {
|
|
||||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
|
||||||
// httpCopy.port = httpPort
|
|
||||||
// httpCopy.protocol = "http"
|
|
||||||
// v2rayConfig.inbounds.add(httpCopy)
|
|
||||||
// }
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
@@ -236,7 +240,7 @@ object V2rayConfigManager {
|
|||||||
|
|
||||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||||
rulesetItems?.forEach { key ->
|
rulesetItems?.forEach { key ->
|
||||||
if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) {
|
if (key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) {
|
||||||
key.domain?.forEach {
|
key.domain?.forEach {
|
||||||
if (it != GEOSITE_PRIVATE
|
if (it != GEOSITE_PRIVATE
|
||||||
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
|
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
|
||||||
@@ -330,11 +334,11 @@ object V2rayConfigManager {
|
|||||||
remoteDns.forEach {
|
remoteDns.forEach {
|
||||||
servers.add(it)
|
servers.add(it)
|
||||||
}
|
}
|
||||||
if (proxyDomain.size > 0) {
|
if (proxyDomain.isNotEmpty()) {
|
||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
address = remoteDns.first(),
|
address = remoteDns.first(),
|
||||||
domains = proxyDomain,
|
domains = proxyDomain,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -344,7 +348,7 @@ object V2rayConfigManager {
|
|||||||
val directDomain = userRule2Domain(TAG_DIRECT)
|
val directDomain = userRule2Domain(TAG_DIRECT)
|
||||||
val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
|
val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
|
||||||
val geoipCn = arrayListOf(GEOIP_CN)
|
val geoipCn = arrayListOf(GEOIP_CN)
|
||||||
if (directDomain.size > 0) {
|
if (directDomain.isNotEmpty()) {
|
||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
address = domesticDns.first(),
|
address = domesticDns.first(),
|
||||||
@@ -366,9 +370,23 @@ object V2rayConfigManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//User DNS hosts
|
||||||
|
try {
|
||||||
|
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
|
||||||
|
if (userHosts.isNotNullEmpty()) {
|
||||||
|
var userHostsMap = userHosts?.split(",")
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.filter { it.contains(":") }
|
||||||
|
?.associate { it.split(":").let { (k, v) -> k to v } }
|
||||||
|
if (userHostsMap != null) hosts.putAll(userHostsMap)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
//block dns
|
//block dns
|
||||||
val blkDomain = userRule2Domain(TAG_BLOCKED)
|
val blkDomain = userRule2Domain(TAG_BLOCKED)
|
||||||
if (blkDomain.size > 0) {
|
if (blkDomain.isNotEmpty()) {
|
||||||
hosts.putAll(blkDomain.map { it to LOOPBACK })
|
hosts.putAll(blkDomain.map { it to LOOPBACK })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,10 +394,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对象
|
||||||
@@ -419,9 +439,11 @@ 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
|
||||||
|
} else if (outbound.streamSettings?.network == NetworkType.XHTTP.type) {
|
||||||
|
muxEnabled = false
|
||||||
}
|
}
|
||||||
if (muxEnabled == true) {
|
if (muxEnabled == true) {
|
||||||
outbound.mux?.enabled = true
|
outbound.mux?.enabled = true
|
||||||
+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
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -222,7 +222,7 @@ object PluginManager {
|
|||||||
return File(pluginDir, pluginId).absolutePath
|
return File(pluginDir, pluginId).absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {
|
fun ComponentInfo.loadString(key: String) = when (val value = metaData.getString(key)) {
|
||||||
is String -> value
|
is String -> value
|
||||||
is Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
|
is Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
|
||||||
.getString(value)
|
.getString(value)
|
||||||
+8
-2
@@ -22,6 +22,7 @@
|
|||||||
package com.v2ray.ang.plugin
|
package com.v2ray.ang.plugin
|
||||||
|
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -33,13 +34,18 @@ abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin()
|
|||||||
|
|
||||||
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
|
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
|
||||||
override val version by lazy {
|
override val version by lazy {
|
||||||
AngApplication.application.getPackageInfo(componentInfo.packageName).versionCode
|
getPackageInfo(componentInfo.packageName).versionCode
|
||||||
}
|
}
|
||||||
override val versionName: String by lazy {
|
override val versionName: String by lazy {
|
||||||
AngApplication.application.getPackageInfo(componentInfo.packageName).versionName!!
|
getPackageInfo(componentInfo.packageName).versionName!!
|
||||||
}
|
}
|
||||||
override val label: CharSequence get() = resolveInfo.loadLabel(AngApplication.application.packageManager)
|
override val label: CharSequence get() = resolveInfo.loadLabel(AngApplication.application.packageManager)
|
||||||
override val icon: Drawable get() = resolveInfo.loadIcon(AngApplication.application.packageManager)
|
override val icon: Drawable get() = resolveInfo.loadIcon(AngApplication.application.packageManager)
|
||||||
override val packageName: String get() = componentInfo.packageName
|
override val packageName: String get() = componentInfo.packageName
|
||||||
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
|
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
|
||||||
|
|
||||||
|
fun getPackageInfo(packageName: String) = AngApplication.application.packageManager.getPackageInfo(
|
||||||
|
packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
|
||||||
|
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
+9
-9
@@ -12,7 +12,6 @@ import android.service.quicksettings.TileService
|
|||||||
import androidx.core.content.ContextCompat
|
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.extension.v2RayApplication
|
|
||||||
import com.v2ray.ang.util.MessageUtil
|
import com.v2ray.ang.util.MessageUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
@@ -41,23 +40,24 @@ class QSTileService : TileService() {
|
|||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
|
||||||
setState(Tile.STATE_INACTIVE)
|
setState(Tile.STATE_INACTIVE)
|
||||||
mMsgReceive = ReceiveMessageHandler(this)
|
mMsgReceive = ReceiveMessageHandler(this)
|
||||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||||
ContextCompat.registerReceiver(applicationContext,mMsgReceive,mFilter,ContextCompat.RECEIVER_EXPORTED)
|
|
||||||
} else {
|
|
||||||
ContextCompat.registerReceiver(applicationContext,mMsgReceive,mFilter,ContextCompat.RECEIVER_NOT_EXPORTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
||||||
+31
-31
@@ -4,6 +4,7 @@ import android.app.Notification
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -44,6 +45,7 @@ object V2RayServiceManager {
|
|||||||
private const val NOTIFICATION_ID = 1
|
private const val NOTIFICATION_ID = 1
|
||||||
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
|
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
|
||||||
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
||||||
|
private const val NOTIFICATION_PENDING_INTENT_RESTART_V2RAY = 2
|
||||||
private const val NOTIFICATION_ICON_THRESHOLD = 3000
|
private const val NOTIFICATION_ICON_THRESHOLD = 3000
|
||||||
|
|
||||||
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
||||||
@@ -148,17 +150,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())
|
||||||
ContextCompat.registerReceiver(
|
|
||||||
service, mMsgReceive, mFilter,
|
|
||||||
ContextCompat.RECEIVER_EXPORTED
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ContextCompat.registerReceiver(
|
|
||||||
service, mMsgReceive, mFilter,
|
|
||||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
@@ -229,11 +221,15 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_STOP -> {
|
AppConfig.MSG_STATE_STOP -> {
|
||||||
|
Log.d(ANG_PACKAGE, "Stop Service")
|
||||||
serviceControl.stopService()
|
serviceControl.stopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_RESTART -> {
|
AppConfig.MSG_STATE_RESTART -> {
|
||||||
startV2rayPoint()
|
Log.d(ANG_PACKAGE, "Restart Service")
|
||||||
|
serviceControl.stopService()
|
||||||
|
Thread.sleep(500L)
|
||||||
|
startV2Ray(serviceControl.getService())
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_MEASURE_DELAY -> {
|
AppConfig.MSG_MEASURE_DELAY -> {
|
||||||
@@ -288,30 +284,24 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
private fun showNotification() {
|
private fun showNotification() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
|
||||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||||
val contentPendingIntent = PendingIntent.getActivity(
|
val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
|
||||||
service,
|
|
||||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
} else {
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
stopV2RayIntent.`package` = ANG_PACKAGE
|
stopV2RayIntent.`package` = ANG_PACKAGE
|
||||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||||
|
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
|
||||||
|
|
||||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(
|
val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
service,
|
restartV2RayIntent.`package` = ANG_PACKAGE
|
||||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
} else {
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val channelId =
|
val channelId =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -335,6 +325,11 @@ object V2RayServiceManager {
|
|||||||
service.getString(R.string.notification_action_stop_v2ray),
|
service.getString(R.string.notification_action_stop_v2ray),
|
||||||
stopV2RayPendingIntent
|
stopV2RayPendingIntent
|
||||||
)
|
)
|
||||||
|
.addAction(
|
||||||
|
R.drawable.ic_delete_24dp,
|
||||||
|
service.getString(R.string.title_service_restart),
|
||||||
|
restartV2RayPendingIntent
|
||||||
|
)
|
||||||
//.build()
|
//.build()
|
||||||
|
|
||||||
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
||||||
@@ -359,7 +354,12 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
fun cancelNotification() {
|
fun cancelNotification() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
service.stopForeground(true)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
service.stopForeground(Service.STOP_FOREGROUND_REMOVE)
|
||||||
|
} else {
|
||||||
|
service.stopForeground(true)
|
||||||
|
}
|
||||||
|
|
||||||
mBuilder = null
|
mBuilder = null
|
||||||
mDisposable?.dispose()
|
mDisposable?.dispose()
|
||||||
mDisposable = null
|
mDisposable = null
|
||||||
+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
-4
@@ -10,6 +10,7 @@ import android.net.LocalSocketAddress
|
|||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
|
import android.net.ProxyInfo
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
@@ -34,10 +35,10 @@ import java.lang.ref.SoftReference
|
|||||||
class V2RayVpnService : VpnService(), ServiceControl {
|
class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
companion object {
|
companion object {
|
||||||
private const val VPN_MTU = 1500
|
private const val VPN_MTU = 1500
|
||||||
private const val PRIVATE_VLAN4_CLIENT = "26.26.26.1"
|
private const val PRIVATE_VLAN4_CLIENT = "10.10.10.1"
|
||||||
private const val PRIVATE_VLAN4_ROUTER = "26.26.26.2"
|
private const val PRIVATE_VLAN4_ROUTER = "10.10.10.2"
|
||||||
private const val PRIVATE_VLAN6_CLIENT = "da26:2626::1"
|
private const val PRIVATE_VLAN6_CLIENT = "fc00::10:10:10:1"
|
||||||
private const val PRIVATE_VLAN6_ROUTER = "da26:2626::2"
|
private const val PRIVATE_VLAN6_ROUTER = "fc00::10:10:10:2"
|
||||||
private const val TUN2SOCKS = "libtun2socks.so"
|
private const val TUN2SOCKS = "libtun2socks.so"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +191,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
builder.setMetered(false)
|
builder.setMetered(false)
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY)) {
|
||||||
|
builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOOPBACK, SettingsManager.getHttpPort()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new interface using the builder and save the parameters.
|
// Create a new interface using the builder and save the parameters.
|
||||||
+9
@@ -21,6 +21,7 @@ import java.io.File
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class AboutActivity : BaseActivity() {
|
class AboutActivity : BaseActivity() {
|
||||||
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
||||||
private val extDir by lazy { File(Utils.backupPath(this)) }
|
private val extDir by lazy { File(Utils.backupPath(this)) }
|
||||||
@@ -87,6 +88,14 @@ class AboutActivity : BaseActivity() {
|
|||||||
binding.layoutFeedback.setOnClickListener {
|
binding.layoutFeedback.setOnClickListener {
|
||||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||||
}
|
}
|
||||||
|
binding.layoutOssLicenses.setOnClickListener{
|
||||||
|
val webView = android.webkit.WebView(this);
|
||||||
|
webView.loadUrl("file:///android_asset/open_source_licenses.html");
|
||||||
|
android.app.AlertDialog.Builder(this)
|
||||||
|
.setTitle("Open source licenses")
|
||||||
|
.setView(webView)
|
||||||
|
.setPositiveButton("OK", android.content.DialogInterface.OnClickListener { dialog, whichButton -> dialog.dismiss() }).show()
|
||||||
|
}
|
||||||
|
|
||||||
binding.layoutTgChannel.setOnClickListener {
|
binding.layoutTgChannel.setOnClickListener {
|
||||||
Utils.openUri(this, AppConfig.TgChannelUrl)
|
Utils.openUri(this, AppConfig.TgChannelUrl)
|
||||||
+67
-12
@@ -19,19 +19,42 @@ import kotlinx.coroutines.withContext
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class LogcatActivity : BaseActivity() {
|
class LogcatActivity : BaseActivity() {
|
||||||
private val binding by lazy {
|
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||||
ActivityLogcatBinding.inflate(layoutInflater)
|
private val throttleManager = ThrottleManager()
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
title = getString(R.string.title_logcat)
|
title = getString(R.string.title_logcat)
|
||||||
|
|
||||||
logcat(false)
|
logcat(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ThrottleManager {
|
||||||
|
private val throttleMap = mutableMapOf<String, Long>()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val THROTTLE_DURATION = 1000L
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun shouldProcess(key: String): Boolean {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
val lastProcessTime = throttleMap[key] ?: 0L
|
||||||
|
|
||||||
|
return if (currentTime - lastProcessTime > THROTTLE_DURATION) {
|
||||||
|
throttleMap[key] = currentTime
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reset(key: String) {
|
||||||
|
throttleMap.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun logcat(shouldFlushLog: Boolean) {
|
private fun logcat(shouldFlushLog: Boolean) {
|
||||||
binding.pbWaiting.visibility = View.VISIBLE
|
binding.pbWaiting.visibility = View.VISIBLE
|
||||||
|
|
||||||
@@ -44,20 +67,23 @@ class LogcatActivity : BaseActivity() {
|
|||||||
process.waitFor()
|
process.waitFor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val lst = linkedSetOf(
|
val lst = linkedSetOf(
|
||||||
"logcat", "-d", "-v", "time", "-s",
|
"logcat", "-d", "-v", "time", "-s",
|
||||||
"GoLog,tun2socks,$ANG_PACKAGE,AndroidRuntime,System.err"
|
"GoLog,tun2socks,$ANG_PACKAGE,AndroidRuntime,System.err"
|
||||||
)
|
)
|
||||||
|
|
||||||
val process = withContext(Dispatchers.IO) {
|
val process = withContext(Dispatchers.IO) {
|
||||||
Runtime.getRuntime().exec(lst.toTypedArray())
|
Runtime.getRuntime().exec(lst.toTypedArray())
|
||||||
}
|
}
|
||||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
|
||||||
|
val allLogs = process.inputStream.bufferedReader().use { it.readLines() }
|
||||||
|
val filteredLogs = processLogs(allLogs)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.tvLogcat.text = allText
|
updateLogDisplay(filteredLogs)
|
||||||
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
|
|
||||||
binding.pbWaiting.visibility = View.GONE
|
|
||||||
Handler(Looper.getMainLooper()).post { binding.svLogcat.fullScroll(View.FOCUS_DOWN) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.pbWaiting.visibility = View.GONE
|
binding.pbWaiting.visibility = View.GONE
|
||||||
@@ -68,6 +94,36 @@ class LogcatActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun processLogs(logs: List<String>): List<String> {
|
||||||
|
val processedLogs = mutableListOf<String>()
|
||||||
|
var isNotMatch = false
|
||||||
|
|
||||||
|
for (line in logs) {
|
||||||
|
when {
|
||||||
|
line.contains("zxing.NotFoundException", ignoreCase = true) -> {
|
||||||
|
if (!isNotMatch) {
|
||||||
|
if (throttleManager.shouldProcess("NotFoundException")) {
|
||||||
|
processedLogs.add(line)
|
||||||
|
isNotMatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> processedLogs.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedLogs.take(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLogDisplay(logs: List<String>) {
|
||||||
|
binding.tvLogcat.text = logs.joinToString("\n")
|
||||||
|
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
|
||||||
|
binding.pbWaiting.visibility = View.GONE
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
binding.svLogcat.fullScroll(View.FOCUS_DOWN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_logcat, menu)
|
menuInflater.inflate(R.menu.menu_logcat, menu)
|
||||||
@@ -80,12 +136,11 @@ class LogcatActivity : BaseActivity() {
|
|||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.clear_all -> {
|
R.id.clear_all -> {
|
||||||
|
throttleManager.reset("zxing.NotFoundException")
|
||||||
logcat(true)
|
logcat(true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-11
@@ -346,8 +346,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val ret = mainViewModel.exportAllServer()
|
val ret = mainViewModel.exportAllServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
if (ret == 0)
|
if (ret > 0)
|
||||||
toast(R.string.toast_success)
|
toast(getString(R.string.title_export_config_count, ret))
|
||||||
else
|
else
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
@@ -358,13 +358,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.ping_all -> {
|
R.id.ping_all -> {
|
||||||
toast(R.string.connection_test_testing)
|
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||||
mainViewModel.testAllTcping()
|
mainViewModel.testAllTcping()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.real_ping_all -> {
|
R.id.real_ping_all -> {
|
||||||
toast(R.string.connection_test_testing)
|
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||||
mainViewModel.testAllRealPing()
|
mainViewModel.testAllRealPing()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -379,14 +379,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
binding.pbWaiting.show()
|
binding.pbWaiting.show()
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
mainViewModel.removeAllServer()
|
val ret = mainViewModel.removeAllServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
toast(getString(R.string.title_del_config_count, ret))
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -406,7 +407,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -418,14 +419,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
binding.pbWaiting.show()
|
binding.pbWaiting.show()
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
mainViewModel.removeInvalidServer()
|
val ret = mainViewModel.removeInvalidServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
toast(getString(R.string.title_del_config_count, ret))
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -517,7 +519,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
when {
|
when {
|
||||||
count > 0 -> {
|
count > 0 -> {
|
||||||
toast(R.string.toast_success)
|
toast(getString(R.string.title_import_config_count, count))
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,7 +625,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
delay(500L)
|
delay(500L)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(getString(R.string.title_update_config_count, count))
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
+1
-1
@@ -143,7 +143,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
removeServer(guid, position)
|
removeServer(guid, position)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+3
-3
@@ -37,7 +37,7 @@ class RoutingEditActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun bindingServer(rulesetItem: RulesetItem): Boolean {
|
private fun bindingServer(rulesetItem: RulesetItem): Boolean {
|
||||||
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
|
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
|
||||||
binding.chkLocked.isChecked = rulesetItem.looked == true
|
binding.chkLocked.isChecked = rulesetItem.locked == true
|
||||||
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
|
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
|
||||||
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
|
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
|
||||||
binding.etPort.text = Utils.getEditable(rulesetItem.port)
|
binding.etPort.text = Utils.getEditable(rulesetItem.port)
|
||||||
@@ -60,7 +60,7 @@ class RoutingEditActivity : BaseActivity() {
|
|||||||
|
|
||||||
rulesetItem.apply {
|
rulesetItem.apply {
|
||||||
remarks = binding.etRemarks.text.toString()
|
remarks = binding.etRemarks.text.toString()
|
||||||
looked = binding.chkLocked.isChecked
|
locked = binding.chkLocked.isChecked
|
||||||
domain = binding.etDomain.text.toString().takeIf { it.isNotEmpty() }
|
domain = binding.etDomain.text.toString().takeIf { it.isNotEmpty() }
|
||||||
?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||||
ip = binding.etIp.text.toString().takeIf { it.isNotEmpty() }
|
ip = binding.etIp.text.toString().takeIf { it.isNotEmpty() }
|
||||||
@@ -95,7 +95,7 @@ class RoutingEditActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+48
-5
@@ -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)
|
||||||
@@ -102,7 +105,7 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -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()
|
||||||
@@ -131,13 +134,25 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do nothing
|
//do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
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.cancel) { _, _ ->
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun refreshData() {
|
fun refreshData() {
|
||||||
rulesets.clear()
|
rulesets.clear()
|
||||||
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
||||||
+1
-1
@@ -25,7 +25,7 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
|
|||||||
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
|
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
|
||||||
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
|
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
|
||||||
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
|
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
|
||||||
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.looked == true
|
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.locked == true
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener {
|
holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener {
|
||||||
+101
-54
@@ -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.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.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.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.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.XHTTP.type -> config?.xhttpExtra
|
||||||
|
else -> null
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
layout_extra?.visibility =
|
||||||
|
when (networks[position]) {
|
||||||
|
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.XHTTP.type -> {
|
||||||
|
xhttpMode
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
arrayOf("---")
|
arrayOf("---")
|
||||||
}
|
}
|
||||||
@@ -553,7 +600,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
MmkvManager.removeServer(editGuid)
|
MmkvManager.removeServer(editGuid)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+9
-6
@@ -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())
|
||||||
@@ -103,7 +106,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
MmkvManager.removeServer(editGuid)
|
MmkvManager.removeServer(editGuid)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+11
-8
@@ -40,6 +40,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
|
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
|
||||||
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
||||||
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
||||||
|
private val appendHttpProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_APPEND_HTTP_PROXY) }
|
||||||
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||||
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
||||||
|
|
||||||
@@ -57,9 +58,9 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
|
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
|
||||||
|
|
||||||
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
|
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
|
||||||
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
|
||||||
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
||||||
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
||||||
|
private val dnsHosts by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DNS_HOSTS) }
|
||||||
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
|
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
|
||||||
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
||||||
|
|
||||||
@@ -141,11 +142,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
httpPort?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
val nval = any as String
|
|
||||||
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
||||||
@@ -156,6 +153,11 @@ class SettingsActivity : BaseActivity() {
|
|||||||
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
dnsHosts?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
dnsHosts?.summary = nval
|
||||||
|
true
|
||||||
|
}
|
||||||
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
|
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
|
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
|
||||||
@@ -175,6 +177,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
updateMode(MmkvManager.decodeSettingsString(AppConfig.PREF_MODE, VPN))
|
updateMode(MmkvManager.decodeSettingsString(AppConfig.PREF_MODE, VPN))
|
||||||
localDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
localDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
||||||
fakeDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED, false)
|
fakeDns?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED, false)
|
||||||
|
appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false)
|
||||||
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
|
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
|
||||||
vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
|
vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
|
||||||
|
|
||||||
@@ -195,9 +198,9 @@ class SettingsActivity : BaseActivity() {
|
|||||||
autoUpdateInterval?.isEnabled = MmkvManager.decodeSettingsBool(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
autoUpdateInterval?.isEnabled = MmkvManager.decodeSettingsBool(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||||
|
|
||||||
socksPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
socksPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
||||||
httpPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
|
||||||
remoteDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
remoteDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
||||||
domesticDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
domesticDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
||||||
|
dnsHosts?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
|
||||||
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
|
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
|
||||||
|
|
||||||
initSharedPreference()
|
initSharedPreference()
|
||||||
@@ -213,7 +216,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
fragmentInterval,
|
fragmentInterval,
|
||||||
autoUpdateInterval,
|
autoUpdateInterval,
|
||||||
socksPort,
|
socksPort,
|
||||||
httpPort,
|
|
||||||
remoteDns,
|
remoteDns,
|
||||||
domesticDns,
|
domesticDns,
|
||||||
delayTestUrl
|
delayTestUrl
|
||||||
@@ -264,6 +266,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
perAppProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
|
perAppProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
|
||||||
localDns?.isEnabled = vpn
|
localDns?.isEnabled = vpn
|
||||||
fakeDns?.isEnabled = vpn
|
fakeDns?.isEnabled = vpn
|
||||||
|
appendHttpProxy?.isEnabled = vpn
|
||||||
localDnsPort?.isEnabled = vpn
|
localDnsPort?.isEnabled = vpn
|
||||||
vpnDns?.isEnabled = vpn
|
vpnDns?.isEnabled = vpn
|
||||||
if (vpn) {
|
if (vpn) {
|
||||||
+1
-1
@@ -113,7 +113,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+36
-1
@@ -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)
|
||||||
@@ -298,7 +333,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
MmkvManager.removeAssetUrl(item.first)
|
MmkvManager.removeAssetUrl(item.first)
|
||||||
initAssets()
|
initAssets()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+15
-5
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +116,7 @@ class UserAssetUrlActivity : BaseActivity() {
|
|||||||
MmkvManager.removeAssetUrl(editAssetId)
|
MmkvManager.removeAssetUrl(editAssetId)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
+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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user