Compare commits

...

32 Commits

Author SHA1 Message Date
2dust
337889c5f1 up 1.9.28 2024-12-29 14:29:22 +08:00
2dust
244d2d3866 Fix bugs related to routing rules
https://github.com/2dust/v2rayNG/issues/4196
https://github.com/2dust/v2rayNG/issues/4199
2024-12-29 11:06:08 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
c0fed0ba4f Download libv2ray from 2dust/AndroidLibXrayLite (#4200) 2024-12-28 19:46:51 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
affb107b9d Sagernet (#4194)
* switch to sagernet/gomobile

* arguments for rb

* match ndk version

* with more options

* nttld/setup-ndk#518
2024-12-28 19:29:00 +08:00
2dust
f96073af99 Update build.yml 2024-12-25 10:23:30 +08:00
2dust
496a0483d2 up 1.9.27 2024-12-24 17:32:06 +08:00
2dust
e11dca00bb Add release function to build 2024-12-24 17:31:32 +08:00
2dust
fde39bf34e Bug fix for isXray() 2024-12-24 15:03:10 +08:00
kore kas nadar
4f11bae238 Update Luri Bakhtiari translation (#4178) 2024-12-23 17:15:06 +08:00
2dust
f6282ba71f Ignore libhysteria2.so 2024-12-23 09:59:57 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
3edf1f4e1b Refine cache key (#4172) 2024-12-22 13:36:18 +08:00
886963226
41bc064083 Optimization logcat after change quickie-foss (#4171)
After changed quickie-foss, scanning cause lot of log. throttle log com.google.zxing.NotFoundException.
2024-12-22 10:21:55 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
eb8562e6b0 Ignore libtun2socks.so (#4169) 2024-12-22 10:04:23 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
68fbdd92c3 s/versionNameSuffix/applicationIdSuffix (#4168) 2024-12-21 21:08:21 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
02038a5d93 Build and cache libtun2socks and libv2ray (#4167)
* build libtun2socks

* Clean up binaries

* test cache

altrepo for testing

* switch to original repo
2024-12-21 20:35:17 +08:00
2dust
4fb8c2f4b2 org.gradle.jvmargs=-Xmx4096m 2024-12-21 17:54:03 +08:00
2dust
7afffa60c3 Code clean 2024-12-21 17:18:35 +08:00
2dust
0e6c860360 JavaVersion.VERSION_17 2024-12-21 16:43:26 +08:00
2dust
ebfbbfa08b Revert "Update build.yml (#4166)"
This reverts commit b5f182dfec.
2024-12-21 16:08:00 +08:00
886963226
b5f182dfec Update build.yml (#4166) 2024-12-21 14:47:28 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
4cf28d0ad0 Fix split apk (#4165) 2024-12-21 14:21:58 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
149bb049a5 Fix flavor build (#4164)
* Fix license report

* Upload both flavors
2024-12-21 14:09:52 +08:00
2dust
124702f0a2 Fix libs 2024-12-21 11:48:48 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
c1cebe578b fdroid flavor and split abi (#4162) 2024-12-21 11:18:41 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
e46c1ee849 switch to quickie-foss for QR code (#4161)
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2024-12-21 11:18:16 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
70f1743114 Switch to gradle-license-plugin (#4160)
* switch to gradle-license-plugin

* generate licenseReleaseReport
2024-12-21 10:40:20 +08:00
2dust
54d520727e Add signature for build 2024-12-20 15:26:55 +08:00
alphax-hue3682
1f1e4db486 Update persian translate (#4158) 2024-12-20 15:08:29 +08:00
solokot
e536236634 Update Russian translation (#4156) 2024-12-20 11:16:55 +08:00
2dust
140c236da5 Add DNS hosts (Format: domain:address,…)
https://github.com/2dust/v2rayNG/issues/4147
2024-12-19 20:57:24 +08:00
2dust
69ede34274 up 1.9.26 2024-12-19 17:34:55 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
fcf6e22132 Add fastlane metadata for F-Droid (#4121)
* Add fastlane metadata

* fixup! Add fastlane metadata

* hoedown full_description.txt

* Remove title heading 1
2024-12-19 13:03:12 +08:00
47 changed files with 1682 additions and 131 deletions

View File

@@ -1,13 +1,14 @@
name: Build APK
on:
push:
workflow_dispatch:
inputs:
XRAY_CORE_VERSION:
description: 'Xray core version or commit hash'
release_tag:
required: false
type: string
push:
branches:
- master
jobs:
build:
@@ -17,77 +18,124 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Prepare build dir
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:
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: 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
add-to-path: true
link-to-sdk: true
local-cache: true
- name: Restore Android Symlinks
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
run: |
directory="${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin"
find "$directory" -type l | while read link; do
current_target=$(readlink "$link")
new_target="$directory/$(basename "$current_target")"
ln -sf "$new_target" "$link"
echo "Changed $(basename "$link") from $current_target to $new_target"
done
- 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: Download libv2ray
uses: robinraju/release-downloader@v1
with:
repository: '2dust/AndroidLibXrayLite'
latest: true
fileName: 'libv2ray.aar'
out-file-path: V2rayNG/app/libs/
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version: '1.23.2'
cache: false
- name: Patch Go use 600296
#https://go-review.googlesource.com/c/go/+/600296
run: |
cd "$(go env GOROOT)"
curl "https://go-review.googlesource.com/changes/go~600296/revisions/5/patch" | base64 -d | patch --verbose -p 1
- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240806205939-81131f6468ab
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Setup Android environment
uses: android-actions/setup-android@v3
- name: Build dependencies
run: |
mkdir ${{ github.workspace }}/build
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
go mod tidy -v
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
- 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
run: |
cd ${{ github.workspace }}/V2rayNG
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
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
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
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
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
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: x86-apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*x86*.apk
- name: Upload Other APKs
uses: actions/upload-artifact@v4
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*x86*.apk
- name: Upload to release
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
name: others-apk
path: |
${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug
!${{ 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
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*playstore*/release/*.apk
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true

16
.github/workflows/fastlane.yml vendored Normal file
View File

@@ -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

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
V2rayNG/app/release/output.json
.idea/
.gradle/
libtun2socks.so
libhysteria2.so

View File

@@ -1,7 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("com.google.android.gms.oss-licenses-plugin")
id("com.jaredsburrows.license")
}
android {
@@ -12,20 +12,26 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 621
versionName = "1.9.25"
versionCode = 624
versionName = "1.9.28"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
splits {
abi {
isEnable = true
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
isUniversalApk = true
reset()
if (abiFilterList != null && abiFilterList.isNotEmpty()) {
include(*abiFilterList.toTypedArray())
} else {
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
}
isUniversalApk = abiFilterList.isNullOrEmpty()
}
}
@@ -42,6 +48,19 @@ android {
}
}
flavorDimensions.add("distribution")
productFlavors {
create("fdroid") {
dimension = "distribution"
applicationIdSuffix = ".fdroid"
buildConfigField("String", "DISTRIBUTION", "\"F-Droid\"")
}
create("playstore") {
dimension = "distribution"
buildConfigField("String", "DISTRIBUTION", "\"Play Store\"")
}
}
sourceSets {
getByName("main") {
jniLibs.srcDirs("libs")
@@ -50,34 +69,55 @@ android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
}
applicationVariants.all {
val variant = this
val versionCodes =
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
val isFdroid = variant.productFlavors.any { it.name == "fdroid" }
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
.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
variant.outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
val abi = output.getFilter("ABI") ?: "universal"
output.outputFileName = "v2rayNG_${variant.versionName}-fdroid_${abi}.apk"
if (versionCodes.containsKey(abi)) {
output.versionCodeOverride =
(100 * variant.versionCode + versionCodes[abi]!!).plus(5000000)
} 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 {
@@ -125,7 +165,7 @@ dependencies {
implementation(libs.language.json)
// Intent and Utility Libraries
implementation(libs.quickie.bundled)
implementation(libs.quickie.foss)
implementation(libs.core)
// AndroidX Lifecycle and Architecture Components
@@ -146,6 +186,5 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
testImplementation(libs.org.mockito.mockito.inline)
testImplementation(libs.mockito.kotlin)
// Oss Licenses
implementation(libs.play.services.oss.licenses)
coreLibraryDesugaring(libs.desugar.jdk.libs)
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,7 @@ object AppConfig {
const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_REMOTE_DNS = "pref_remote_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_LOGLEVEL = "pref_core_loglevel"
const val PREF_MODE = "pref_mode"

View File

@@ -95,4 +95,4 @@ inline fun <reified T : Serializable> Intent.serializable(key: String): T? = whe
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
}
inline fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())
fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())

View File

@@ -9,9 +9,11 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerList
import com.v2ray.ang.util.JsonUtil
@@ -91,8 +93,10 @@ object SettingsManager {
fun saveRoutingRuleset(index: Int, ruleset: RulesetItem?) {
if (ruleset == null) return
val rulesetList = MmkvManager.decodeRoutingRulesets()
if (rulesetList.isNullOrEmpty()) return
var rulesetList = MmkvManager.decodeRoutingRulesets()
if (rulesetList.isNullOrEmpty()) {
rulesetList = mutableListOf()
}
if (index < 0 || index >= rulesetList.count()) {
rulesetList.add(0, ruleset)
@@ -113,12 +117,23 @@ object SettingsManager {
}
fun routingRulesetsBypassLan(): Boolean {
val guid = MmkvManager.getSelectServer() ?: return false
val config = MmkvManager.decodeServerConfig(guid) ?: return false
if (config.configType == EConfigType.CUSTOM) {
val raw = MmkvManager.decodeServerRaw(guid) ?: return false
val v2rayConfig = JsonUtil.fromJson(raw, V2rayConfig::class.java)
val exist = v2rayConfig.routing.rules.filter { it.outboundTag == TAG_DIRECT }?.any {
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
}
return exist == true
}
val rulesetItems = MmkvManager.decodeRoutingRulesets()
val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
}
return exist == true
}
}
fun swapRoutingRuleset(fromPosition: Int, toPosition: Int) {
val rulesetList = MmkvManager.decodeRoutingRulesets()

View File

@@ -39,6 +39,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig
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.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
@@ -239,7 +240,7 @@ object V2rayConfigManager {
val rulesetItems = MmkvManager.decodeRoutingRulesets()
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 {
if (it != GEOSITE_PRIVATE
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
@@ -333,7 +334,7 @@ object V2rayConfigManager {
remoteDns.forEach {
servers.add(it)
}
if (proxyDomain.size > 0) {
if (proxyDomain.isNotEmpty()) {
servers.add(
V2rayConfig.DnsBean.ServersBean(
address = remoteDns.first(),
@@ -347,7 +348,7 @@ object V2rayConfigManager {
val directDomain = userRule2Domain(TAG_DIRECT)
val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
val geoipCn = arrayListOf(GEOIP_CN)
if (directDomain.size > 0) {
if (directDomain.isNotEmpty()) {
servers.add(
V2rayConfig.DnsBean.ServersBean(
address = domesticDns.first(),
@@ -369,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
val blkDomain = userRule2Domain(TAG_BLOCKED)
if (blkDomain.size > 0) {
if (blkDomain.isNotEmpty()) {
hosts.putAll(blkDomain.map { it to LOOPBACK })
}

View File

@@ -222,7 +222,7 @@ object PluginManager {
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 Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
.getString(value)

View File

@@ -4,6 +4,7 @@ import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -353,7 +354,12 @@ object V2RayServiceManager {
fun cancelNotification() {
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
mDisposable?.dispose()
mDisposable = null

View File

@@ -20,7 +20,6 @@ import com.v2ray.ang.util.ZipUtil
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
class AboutActivity : BaseActivity() {
@@ -90,7 +89,12 @@ class AboutActivity : BaseActivity() {
Utils.openUri(this, AppConfig.v2rayNGIssues)
}
binding.layoutOssLicenses.setOnClickListener{
startActivity(Intent(this, OssLicensesMenuActivity::class.java))
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 {

View File

@@ -19,19 +19,42 @@ import kotlinx.coroutines.withContext
import java.io.IOException
class LogcatActivity : BaseActivity() {
private val binding by lazy {
ActivityLogcatBinding.inflate(layoutInflater)
}
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
private val throttleManager = ThrottleManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
title = getString(R.string.title_logcat)
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) {
binding.pbWaiting.visibility = View.VISIBLE
@@ -44,20 +67,23 @@ class LogcatActivity : BaseActivity() {
process.waitFor()
}
}
val lst = linkedSetOf(
"logcat", "-d", "-v", "time", "-s",
"GoLog,tun2socks,$ANG_PACKAGE,AndroidRuntime,System.err"
)
val process = withContext(Dispatchers.IO) {
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) {
binding.tvLogcat.text = allText
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
binding.pbWaiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { binding.svLogcat.fullScroll(View.FOCUS_DOWN) }
updateLogDisplay(filteredLogs)
}
} catch (e: IOException) {
withContext(Dispatchers.Main) {
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 {
menuInflater.inflate(R.menu.menu_logcat, menu)
@@ -80,12 +136,11 @@ class LogcatActivity : BaseActivity() {
toast(R.string.toast_success)
true
}
R.id.clear_all -> {
throttleManager.reset("zxing.NotFoundException")
logcat(true)
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -387,7 +387,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
@@ -407,7 +407,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
@@ -427,7 +427,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()

View File

@@ -143,7 +143,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
.setPositiveButton(android.R.string.ok) { _, _ ->
removeServer(guid, position)
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()

View File

@@ -95,7 +95,7 @@ class RoutingEditActivity : BaseActivity() {
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()

View File

@@ -105,7 +105,7 @@ class RoutingSettingActivity : BaseActivity() {
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
@@ -134,7 +134,7 @@ class RoutingSettingActivity : BaseActivity() {
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do nothing
}
.show()
@@ -189,7 +189,7 @@ class RoutingSettingActivity : BaseActivity() {
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do nothing
}
.show()

View File

@@ -600,7 +600,7 @@ class ServerActivity : BaseActivity() {
MmkvManager.removeServer(editGuid)
finish()
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()

View File

@@ -106,7 +106,7 @@ class ServerCustomConfigActivity : BaseActivity() {
MmkvManager.removeServer(editGuid)
finish()
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()

View File

@@ -60,6 +60,7 @@ class SettingsActivity : BaseActivity() {
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_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 mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
@@ -152,6 +153,11 @@ class SettingsActivity : BaseActivity() {
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
true
}
dnsHosts?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
dnsHosts?.summary = nval
true
}
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
@@ -194,6 +200,7 @@ class SettingsActivity : BaseActivity() {
socksPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
remoteDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
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)
initSharedPreference()

View File

@@ -113,7 +113,7 @@ class SubEditActivity : BaseActivity() {
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()

View File

@@ -333,7 +333,7 @@ class UserAssetActivity : BaseActivity() {
MmkvManager.removeAssetUrl(item.first)
initAssets()
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()

View File

@@ -116,7 +116,7 @@ class UserAssetUrlActivity : BaseActivity() {
MmkvManager.removeAssetUrl(editAssetId)
finish()
}
.setNegativeButton(android.R.string.no) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()

View File

@@ -498,7 +498,7 @@ object Utils {
ContextCompat.RECEIVER_NOT_EXPORTED
}
fun isXray(): Boolean = (ANG_PACKAGE == "com.v2ray.ang")
fun isXray(): Boolean = (ANG_PACKAGE.startsWith("com.v2ray.ang"))
}

View File

@@ -31,6 +31,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_VPN_DNS,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_DNS_HOSTS,
AppConfig.PREF_DELAY_TEST_URL,
AppConfig.PREF_LOCAL_DNS_PORT,
AppConfig.PREF_SOCKS_PORT,

View File

@@ -180,6 +180,9 @@
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (Format: domain:address,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">True delay test url (http/https)</string>
<string name="summary_pref_delay_test_url">Url</string>

View File

@@ -180,6 +180,9 @@
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (Format: domain:address,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">সঠিক বিলম্ব পরীক্ষা ইউআরএল (http/https)</string>
<string name="summary_pref_delay_test_url">ইউআরএল</string>

View File

@@ -120,7 +120,7 @@
<string name="server_lab_port_hop_interval">فاسله پورت گوم (سانیه)</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_xhttp_mode">هالت XHTTP</string>
<string name="server_lab_xhttp_extra">XHTTP قلوه خام JSON، قالوو: { XHTTPObject }</string>
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">هون بار ونی بۊ</string>
@@ -180,6 +180,9 @@
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: آدرس،...)</string>
<string name="summary_pref_dns_hosts">دامنه:آدرس،...</string>
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تئخیر واقعی (http/https)</string>
<string name="summary_pref_delay_test_url">نشۊوی اینترنتی</string>

View File

@@ -115,7 +115,7 @@
<string name="server_lab_port_hop_interval">فاصله پورت پرش (ثانیه)</string>
<string name="server_lab_stream_pinsha256">PINSHA256</string>
<string name="server_lab_xhttp_mode">حالت XHTTP</string>
<string name="server_lab_xhttp_extra">جیسون خام XHTTP EXTRA، فرمت: { XHTTPObject }</string>
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
<!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">آدرس اینترنتی را اضافه کنید</string>
@@ -178,6 +178,9 @@
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS مستقیم هاست(فرمت: دامنه: آدرس،…)</string>
<string name="summary_pref_dns_hosts">دامنه: آدرس، …</string>
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تاخیر واقعی کانفیگ ها (HTTP/HTTPS)</string>
<string name="summary_pref_delay_test_url">URL</string>

View File

@@ -179,11 +179,14 @@
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">Узлы DNS (формат: домен:адрес,…)</string>
<string name="summary_pref_dns_hosts">домен:адрес,…</string>
<string name="title_pref_delay_test_url">Сервис проверки времени отклика (HTTP/HTTPS)</string>
<string name="summary_pref_delay_test_url">URL</string>
<string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string>
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать прокси по протоколам SOCKS/HTTP. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать локальный прокси. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в доверенной сети</string>
<string name="title_pref_allow_insecure">Разрешать небезопасные</string>
@@ -289,7 +292,7 @@
<string name="connection_test_pending">Проверить подключение</string>
<string name="connection_test_testing">Проверка…</string>
<string name="connection_test_testing_count">Проверено профилей: %d</string>
<string name="connection_test_testing_count">Проверка профилей: %d</string>
<string name="connection_test_available">Успешно: HTTP-соединение заняло %d мс</string>
<string name="connection_test_error">Сбой проверки интернет-соединения: %s</string>
<string name="connection_test_fail">Интернет недоступен</string>

View File

@@ -179,6 +179,9 @@
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (Format: domain:address,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">URL kiểm tra độ trễ thực (HTTP / HTTPS)</string>
<string name="summary_pref_delay_test_url">URL</string>

View File

@@ -176,6 +176,9 @@
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (格式: 域名:地址,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">真连接延迟测试网址 (http/https)</string>
<string name="summary_pref_delay_test_url">Url</string>

View File

@@ -175,8 +175,8 @@
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
<string name="title_pref_domestic_dns">國內 DNS (可選)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">真連線延遲測試網址 (http/https)</string>
<string name="summary_pref_delay_test_url">Url</string>

View File

@@ -182,6 +182,9 @@
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (Format: domain:address,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">True delay test url (http/https)</string>
<string name="summary_pref_delay_test_url">Url</string>

View File

@@ -190,6 +190,11 @@
android:summary="@string/summary_pref_domestic_dns"
android:title="@string/title_pref_domestic_dns" />
<EditTextPreference
android:key="pref_dns_hosts"
android:summary="@string/summary_pref_dns_hosts"
android:title="@string/title_pref_dns_hosts" />
<EditTextPreference
android:key="pref_delay_test_url"
android:summary="@string/summary_pref_delay_test_url"

View File

@@ -7,7 +7,7 @@ plugins {
buildscript {
dependencies {
classpath(libs.oss.licenses.plugin)
classpath(libs.gradle.license.plugin)
}
}

View File

@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects

View File

@@ -1,5 +1,7 @@
[versions]
agp = "8.7.2"
desugar_jdk_libs = "2.1.4"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.0"
coreKtx = "1.15.0"
junit = "4.13.2"
@@ -11,14 +13,12 @@ activity = "1.9.3"
constraintlayout = "2.2.0"
mmkvStatic = "1.3.11"
gson = "2.11.0"
ossLicensesPlugin = "0.10.6"
playServicesOssLicenses = "17.1.0"
quickieFoss = "1.13.1"
rxjava = "3.1.9"
rxandroid = "3.0.2"
rxpermissions = "0.12"
toastcompat = "1.1.0"
editorkit = "2.9.0"
quickieBundled = "1.10.0"
core = "3.5.3"
workRuntimeKtx = "2.10.0"
lifecycleViewmodelKtx = "2.8.7"
@@ -29,6 +29,8 @@ preferenceKtx = "1.2.1"
recyclerview = "1.3.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
gradle-license-plugin = { module = "com.jaredsburrows:gradle-license-plugin", version.ref = "gradleLicensePlugin" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -38,8 +40,7 @@ androidx-activity = { group = "androidx.activity", name = "activity", version.re
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
oss-licenses-plugin = { module = "com.google.android.gms:oss-licenses-plugin", version.ref = "ossLicensesPlugin" }
play-services-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "playServicesOssLicenses" }
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
rxpermissions = { module = "com.github.tbruyelle:rxpermissions", version.ref = "rxpermissions" }
@@ -47,7 +48,6 @@ toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastc
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
language-json = { module = "com.blacksquircle.ui:language-json", version.ref = "editorkit" }
quickie-bundled = { module = "io.github.g00fy2.quickie:quickie-bundled", version.ref = "quickieBundled" }
core = { module = "com.google.zxing:core", version.ref = "core" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workRuntimeKtx" }

View File

@@ -16,7 +16,6 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
jcenter()
maven { url = uri("https://jitpack.io") }
}
}

View File

@@ -0,0 +1,27 @@
<p>A V2Ray client for Android, support <a href="https://github.com/XTLS/Xray-core">Xray core</a> and <a href="https://github.com/v2fly/v2ray-core">v2fly core</a></p>
<h3>Telegram Channel</h3>
<p><a href="https://t.me/github_2dust">github_2dust</a></p>
<h3>Usage</h3>
<h4>Geoip and Geosite</h4>
<ul>
<li>geoip.dat and geosite.dat files are in <code>Android/data/com.v2ray.ang/files/assets</code> (path may differ on some Android device)</li>
<li>download feature will get enhanced version in this <a href="https://github.com/Loyalsoldier/v2ray-rules-dat">repo</a> (Note it need a working proxy)</li>
<li>latest official <a href="https://github.com/v2fly/domain-list-community">domain list</a> and <a href="https://github.com/v2fly/geoip">ip list</a> can be imported manually</li>
<li>possible to use third party dat file in the same folder, like <a href="https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6">h2y</a></li>
</ul>
<h3>More in our <a href="https://github.com/2dust/v2rayNG/wiki">wiki</a></h3>
<h3>Development guide</h3>
<p>Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
The aar can be compiled from the Golang project <a href="https://github.com/2dust/AndroidLibV2rayLite">AndroidLibV2rayLite</a> or <a href="https://github.com/2dust/AndroidLibXrayLite">AndroidLibXrayLite</a>.
For a quick start, read guide for <a href="https://github.com/golang/go/wiki/Mobile">Go Mobile</a> and <a href="https://tutorialedge.net/golang/makefiles-for-go-developers/">Makefiles for Go Developers</a></p>
<p>v2rayNG can run on Android Emulators. For WSA, VPN permission need to be granted via
<code>appops set [package name] ACTIVATE_VPN allow</code></p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1 @@
A V2Ray client for Android, support Xray core and v2fly core

View File

@@ -0,0 +1 @@
v2rayNG