Compare commits

...

72 Commits

Author SHA1 Message Date
2dust
f252d1395a up 1.9.19 2024-11-21 20:30:35 +08:00
2dust
2dc0472c69 When creating a new routing rule, add it to the top 2024-11-21 20:24:01 +08:00
hosêyň abāspanā
3e09adc4d1 Improved Luri Bakhtiari Translation (#4003) 2024-11-21 09:26:08 +08:00
phoenix6936
11750b9382 Improved Persian translation (#4001)
* Improved Persian translation

Improved Persian translation

* Improved Persian translation

Improved Persian translation
2024-11-21 09:25:48 +08:00
solokot
30a4c2199a Improved Russian translation (#3997) 2024-11-20 16:00:14 +08:00
2dust
406a9f996e up 1.9.18 2024-11-20 09:40:08 +08:00
Chocolate4U
5373579bd5 Import rulesets from qrcode (#3991)
* Renamed functions to be more semantically accurate

* Import rulesets from qrcode

Add capability to import rulesets from qrcode
fixes and improvements
2024-11-20 09:23:08 +08:00
Tamim Hossain
9b4cc201e7 Add preSharedKey support and fix parsing function #3512 (#3989)
Add support for preSharedKey in WireGuard configurations and fix the parsing function to correctly handle all necessary fields.

Previously, the application did not support the optional preSharedKey parameter in WireGuard config files, forcing users to rely on JSON custom configurations. This update introduces a dedicated field for preSharedKey in the UI, aligning with Xray Core's support and simplifying the setup process for users.

Changes include:
- Added `preSharedKey` field in the WireGuard UI configuration.
- Updated `parseWireguardConfFile` function to correctly parse `PrivateKey`, `PublicKey`, and `preSharedKey`.
- Ensured `preSharedKey` is optional and handled gracefully when absent.
- Updated `toOutbound` method to include `preSharedKey` in the outbound configuration.
- Set `remarks` to the current Unix time during parsing.

Tested with the following configuration:
```[Interface]
Address = 192.168.6.66/32
DNS = 1.1.1.1,8.8.8.8
PrivateKey = eD/6cpJQaEeDH05AMeFyN3KSLLX+7YFR+MYRdgPDQ3Y=
[Peer]
publickey=/HS7r3waPuU7tTBLd2FlBhC+VROpJ5bwh5XXxuOoKFs=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = sg3.vpnjantit.com:1024
```
Resolves #3512
2024-11-20 09:15:11 +08:00
2dust
6f2c96c2b6 Bug fix
https://github.com/2dust/v2rayNG/issues/3988
2024-11-19 20:38:44 +08:00
solokot
1f6104de8b Update Russian translation (#3987) 2024-11-19 19:07:13 +08:00
2dust
e078a2ab27 up 1.9.17 2024-11-19 17:30:33 +08:00
2dust
5695c17908 Code optimization 2024-11-19 14:28:58 +08:00
decorativeman
e2c1081d5a Update Persian Translation (#3980)
* Update persian translate 

Update persian translate

* Update strings.xml
2024-11-19 13:49:50 +08:00
hosêyň abāspanā
bcbcbc91c7 Update Luri Bakhtiari translation (#3985) 2024-11-19 13:49:34 +08:00
2dust
5eb3566e8d Add libhysteria2.so 2024-11-19 11:38:05 +08:00
decorativeman
d75eca8dd4 Update gradle-wrapper.properties (#3969)
Update Gradle 8.9 to 8.11
2024-11-19 10:58:11 +08:00
2dust
640c16d8dc Improve Fmt 2024-11-19 10:45:01 +08:00
2dust
1ba5c5a7a6 Add xhttp extra 2024-11-19 10:17:03 +08:00
2dust
f67af69dda Improve Utils 2024-11-19 10:09:03 +08:00
2dust
5d47777307 Add xhttp mode 2024-11-18 20:25:13 +08:00
Tamim Hossain
ab22bb9804 Updated the WorkManager configuration to replace BuildConfig.APPLICATION_ID with ANG_PACKAGE for setting the default process name. This ensures consistent configuration handling in background processes. (#3970)
Changes:
- Modified `setDefaultProcessName` to use `ANG_PACKAGE` instead of `BuildConfig.APPLICATION_ID`.

This resolves inconsistencies in process naming conventions and aligns with project requirements.
2024-11-18 19:26:11 +08:00
Tamim Hossain
2626462e49 Remove unnecessary Context parameter from setNightMode (#3971)
Refactored the `setNightMode` function to remove the unused `Context` parameter.

Changes:
- Eliminated the `context` parameter from the `setNightMode` function.
- Adjusted function signature to align with the current implementation, which does not utilize the `Context`.

This simplifies the function interface and ensures cleaner, more maintainable code.
2024-11-18 19:26:00 +08:00
2dust
41893d79c0 SplitHTTP is now XHTTP 2024-11-18 18:58:15 +08:00
2dust
834c1ba63d Remove quic 2024-11-18 18:39:06 +08:00
2dust
633ee63891 Improved duplicate configuration
https://github.com/2dust/v2rayNG/issues/3948
2024-11-16 20:18:37 +08:00
hosêyň abāspanā
eb5627c0d0 Update strings.xml (#3955) 2024-11-16 09:28:52 +08:00
hosêyň abāspanā
6db38f6e3d Update arrays.xml (#3954)
Rename بختیاری to لۊری بختیاری
2024-11-15 18:31:01 +08:00
Tamim Hossain
c69a758429 Add Bakhtiari language support and fix implementation issues (#3952)
This commit improves the Bakhtiari language support by addressing issues from PR [#3927](https://github.com/2dust/v2rayNG/pull/3927), where the initial implementation had incorrect file placements and lacked necessary changes for functionality.

- Integrated `strings.xml` into the correct `values-bqi-rIR` directory.
- Updated the `Language` enum to include `BAKHTIARI("bqi-rIR")`.
- Modified the `getLocale()` function to handle Bakhtiari with `Locale("bqi", "IR")`.
- Added Bakhtiari to the `language_select` string-array using its native script: `<item>بختیاری</item>`.
- Updated the `language_select_value` string-array to include `<item>bqi-rIR</item>`.

Verified that the language switching works correctly. For grammatical or translation accuracy, a native speaker's review is needed.

@hosseinabaspanah, since I assume Bakhtiari is your native language, could you please review the translations to ensure accuracy?
2024-11-15 17:15:10 +08:00
Tamim Hossain
cee3a0ffec Rename styles.xml and themes.xml for consistency with Android Studio templates (#3950)
Updated the naming of `styles.xml` and `themes.xml` to align with the new Android Studio template conventions. This follows up on commit `18c0143` where I introduced the new android studio project template.

- Verified and tested the changes thoroughly to ensure that the app behaves as expected, with no regressions.
- Ensured all affected references and dependencies were updated accordingly.

This keeps the project consistent with modern Android development practices and improves maintainability.
2024-11-15 17:07:30 +08:00
Tamim Hossain
18c0143186 Upgrade project to new Android Studio template and migrate Java code to Kotlin (#3937)
### Summary
- Updated the project structure using the latest Android Studio template.
- Migrated portions of the codebase from Java to Kotlin for improved readability and maintainability.

### Details
- Refactored and reorganized files according to the new Android Studio project template to ensure compatibility with the latest project standards.
- Migrated key Java classes to Kotlin, adopting Kotlin idioms and improving type safety.
- Verified that core functionalities remain intact after migration and update.
- Removed redundant Java files and updated imports where necessary.

### Notes
- Further Kotlin migration may be needed as additional Java files are reviewed.
- Test thoroughly to confirm that all functionalities work as expected after these changes.
2024-11-15 13:42:46 +08:00
Chocolate4U
bbf0b05b49 Add Assets From QRcode (#3933)
Add Capability to Import Geo Assets From QRcode
2024-11-13 18:49:35 +08:00
2dust
44723c56ad up 1.9.16 2024-11-12 14:34:40 +08:00
2dust
e53c36b53b Bug fix 2024-11-12 14:33:29 +08:00
TTG
80f26cd4b8 Update DNS in Routing and Configurations (#3921)
* Update DNS configs

* Update DNS in AppConfig
2024-11-12 09:41:54 +08:00
2dust
b023414cd0 Fix UI
https://github.com/2dust/v2rayNG/issues/3866
2024-11-10 19:57:06 +08:00
2dust
2f56104565 Fix UI
https://github.com/2dust/v2rayNG/issues/3892
2024-11-10 19:37:21 +08:00
2dust
9cd5fefdca up 1.9.15 2024-11-10 09:58:08 +08:00
2dust
25c42c475f newFixedThreadPool(Runtime.getRuntime().availableProcessors()) 2024-11-09 19:38:59 +08:00
DecorativeFamily
96e66da071 Update persian translate (#3913)
* Update persian translate

Update persian translate

* Update persian translate 

Update persian translate
2024-11-09 18:31:36 +08:00
2dust
6914b9ee1b Bug fix
https://github.com/2dust/v2rayNG/issues/3911
2024-11-09 17:32:13 +08:00
TTG
cfc6546c97 Update Configuration to Optimize (#3912)
* Remove geolocation-cn

* Segment and update DNS lists
2024-11-09 14:40:24 +08:00
2dust
0880313659 Bug fix
https://github.com/2dust/v2rayNG/issues/3885
2024-11-08 15:06:52 +08:00
2dust
875ca02126 Bug fix
https://github.com/2dust/v2rayNG/issues/3900
2024-11-08 10:43:13 +08:00
2dust
c2d5925053 up 1.9.14 2024-11-07 20:54:09 +08:00
2dust
547bbf8e95 Bug fix 2024-11-07 20:37:17 +08:00
2dust
2218251b03 Bug fix
https://github.com/2dust/v2rayNG/issues/3895
2024-11-07 20:32:08 +08:00
2dust
4da3a23162 up 1.9.13 2024-11-06 14:31:10 +08:00
2dust
28a90baf88 Bug fix
https://github.com/2dust/v2rayNG/issues/3883
2024-11-06 11:22:01 +08:00
2dust
7bbdda2f2f This is a temporary solution
https://github.com/2dust/v2rayNG/issues/3883
2024-11-05 21:32:50 +08:00
2dust
884b444a41 up 1.9.12 2024-11-05 19:01:03 +08:00
2dust
e1ff2df36e Bug fix 2024-11-05 18:59:05 +08:00
2dust
61c0111778 Revert "Refactor listenForPackageChanges to remove redundant registerReceiver calls (#3872)"
This reverts commit 5f167512f5.
2024-11-05 18:07:55 +08:00
Tamim Hossain
bfc9a64e07 Correct isEmpty syntax for collection check (#3881)
Fixed the syntax for checking if `filesToCompress` is empty by using `isEmpty()` instead of `isEmpty`. This ensures correct functionality when verifying if the collection has elements.
2024-11-05 18:01:32 +08:00
Tamim Hossain
2ba92045cc Remove unnecessary Boolean comparisons in conditional checks (#3880)
Simplified conditional checks by removing unnecessary `== true` comparisons. The `decodeSettingsBool` function returns a non-nullable Boolean with a default value, so direct usage improves readability and keeps the code concise.
2024-11-05 18:00:52 +08:00
Tamim Hossain
aeca9f51c8 Remove redundant Boolean comparison in runLoop call (#3879)
Simplified the call to `runLoop` by removing the redundant `== true` comparison. Since `decodeSettingsBool` returns a non-nullable Boolean, direct usage improves readability.
2024-11-05 18:00:23 +08:00
Tamim Hossain
b107c0ac1d Remove redundant TAG field in ProcessService (#3878)
Refactored `ProcessService` by removing the redundant `TAG` variable and using `ANG_PACKAGE` directly in logging calls, simplifying the code and reducing unnecessary field assignments.
2024-11-05 17:59:37 +08:00
Tamim Hossain
65a04b4784 Refactor getString call to use orEmpty for null safety (#3877)
Updated `getString` call to use `orEmpty()` instead of specifying a default empty string, making the code cleaner and handling nullability more effectively.
2024-11-05 17:58:23 +08:00
Tamim Hossain
eab9f50cfd Refactor BootReceiver for improved null handling and readability (#3876)
Refactored `BootReceiver` to simplify null checks and conditional structure. Combined context and intent checks into a single early return and refactored logic for `decodeStartOnBoot` and `getSelectServer` to improve readability.
2024-11-05 17:57:51 +08:00
Tamim Hossain
b8bb83b524 Used safecall ? (#3874)
Used safecall `?`
2024-11-05 17:56:32 +08:00
Tamim Hossain
93eb9fe3b9 Introduce NetworkType enum to improve network type handling (#3873)
* Introduce NetworkType enum to improve network type handling

Created a `NetworkType` enum to represent various network types, improving readability and reducing potential errors caused by hardcoded string comparisons. Updated the `getQueryDic` function to utilize this enum.

* Refactor to use NetworkType enum in VmessFmt

Replaced hardcoded network type strings with the `NetworkType` enum in `VmessFmt` functions. Updated `parse`, `toUri`, and `parseVmessStd` methods to use `NetworkType.fromString`, improving readability and reducing errors caused by typos in network type strings.
2024-11-05 17:56:04 +08:00
Tamim Hossain
5f167512f5 Refactor listenForPackageChanges to remove redundant registerReceiver calls (#3872)
Refactored the `listenForPackageChanges` function to remove redundant calls to `registerReceiver` by creating a single `IntentFilter` instance. This simplifies the code and improves readability.
2024-11-05 17:55:08 +08:00
Tamim Hossain
a727b81263 Update registerReceiver usage to comply with Android Tiramisu+ guidelines (#3871)
### Summary
- Updated `registerReceiver` usage to align with Android Tiramisu+ documentation.

### Details
- Replaced direct `registerReceiver` calls with `ContextCompat.registerReceiver` for improved compatibility.
- Used `RECEIVER_EXPORTED` and `RECEIVER_NOT_EXPORTED` flags based on API level to ensure correct receiver permissions.
- Added reference to the official Android documentation for `registerReceiver`.

### References
- [Documentation on registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int))

This commit ensures that the `registerReceiver` call is consistent with the latest Android standards, improving compatibility and security across Android versions.
2024-11-05 16:42:17 +08:00
solokot
f27c9192d1 Update Russian translation (#3870) 2024-11-05 16:39:19 +08:00
DecorativeFamily
90153fa17f Update persian translate (#3869)
* Update persian translate

* Update strings.xml
2024-11-05 16:38:54 +08:00
DecorativeFamily
cdfaa01852 Revert "Update persian translate (#3867)" (#3868)
This reverts commit 33d2c3b00d.
2024-11-05 15:05:42 +08:00
DecorativeFamily
33d2c3b00d Update persian translate (#3867) 2024-11-05 15:03:35 +08:00
DecorativeFamily
b7f992cdc0 Update gradle-wrapper.properties (#3844)
Gradle 8.10.2
2024-11-05 15:03:21 +08:00
DecorativeFamily
0a6a24e309 Gradle (#3839)
* Create dependabot.yml

* Update dependabot.yml

* Update libs.versions.toml

* Delete .github/dependabot.yml
2024-11-05 15:03:07 +08:00
2dust
153b4cffef Unable to obtain the notification permission 2024-11-05 14:29:44 +08:00
2dust
f09a413232 Bug fix
https://github.com/2dust/v2rayNG/issues/3858
2024-11-05 10:24:06 +08:00
886963226
cba58f6ae2 Update V2rayConfigManager.kt (#3860) 2024-11-05 09:31:01 +08:00
2dust
2bf4b91488 Bug fix
https://github.com/2dust/v2rayNG/issues/3852
2024-11-04 20:22:40 +08:00
146 changed files with 1953 additions and 1116 deletions

View File

@@ -1,19 +1,20 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.v2ray.ang"
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 605
versionName = "1.9.11"
versionCode = 615
versionName = "1.9.19"
multiDexEnabled = true
splits {
abi {
isEnable = true
@@ -27,20 +28,16 @@ android {
}
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildTypes {
release {
isMinifyEnabled = false
}
debug {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
@@ -50,8 +47,13 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
jvmTarget = JavaVersion.VERSION_11.toString()
}
applicationVariants.all {
@@ -69,7 +71,8 @@ android {
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
if (versionCodes.containsKey(abi)) {
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
output.versionCodeOverride =
(1000000 * versionCodes[abi]!!).plus(variant.versionCode)
} else {
return@forEach
}
@@ -86,49 +89,60 @@ android {
useLegacyPackaging = true
}
}
}
dependencies {
// Core Libraries
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
implementation(libs.constraintlayout)
implementation(libs.legacy.support.v4)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.cardview)
// AndroidX Core Libraries
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.preference.ktx)
implementation(libs.recyclerview)
implementation(libs.fragment.ktx)
implementation(libs.multidex)
implementation(libs.viewpager2)
// Androidx ktx
implementation(libs.activity.ktx)
// UI Libraries
implementation(libs.material)
implementation(libs.toastcompat)
implementation(libs.editorkit)
implementation(libs.flexbox)
// Data and Storage Libraries
implementation(libs.mmkv.static)
implementation(libs.gson)
// Reactive and Utility Libraries
implementation(libs.rxjava)
implementation(libs.rxandroid)
implementation(libs.rxpermissions)
// Language and Processing Libraries
implementation(libs.language.base)
implementation(libs.language.json)
// Intent and Utility Libraries
implementation(libs.quickie.bundled)
implementation(libs.core)
// AndroidX Lifecycle and Architecture Components
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.runtime.ktx)
//kotlin
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)
// Background Task Libraries
implementation(libs.work.runtime.ktx)
implementation(libs.work.multiprocess)
}
// Multidex Support
implementation(libs.multidex)
// Testing Libraries
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
testImplementation(libs.org.mockito.mockito.inline)
testImplementation(libs.mockito.kotlin)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -41,14 +41,90 @@
"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",
"outboundTag": "proxy",
"ip": [
"1.0.0.1",
"1.1.1.1",
"8.8.8.8",
"8.8.4.4",
"geoip:facebook",
"geoip:fastly",
"geoip:google",

View File

@@ -34,29 +34,62 @@
"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",
"outboundTag": "direct",
"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"
]
},
@@ -64,13 +97,7 @@
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
"geosite:cn"
]
},
{

View File

@@ -7,6 +7,7 @@ import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import androidx.work.WorkManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
@@ -22,7 +23,7 @@ class AngApplication : MultiDexApplication() {
}
private val workManagerConfiguration: Configuration = Configuration.Builder()
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
.setDefaultProcessName("${ANG_PACKAGE}:bg")
.build()
override fun onCreate() {
@@ -37,7 +38,7 @@ class AngApplication : MultiDexApplication() {
MMKV.initialize(this)
Utils.setNightMode(application)
Utils.setNightMode()
// Initialize WorkManager with the custom configuration
WorkManager.initialize(this, workManagerConfiguration)

View File

@@ -161,15 +161,20 @@ object AppConfig {
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
// 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_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_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_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_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_SECURITY = "auto"

View File

@@ -8,7 +8,8 @@ enum class Language(val code: String) {
VIETNAMESE("vi"),
RUSSIAN("ru"),
PERSIAN("fa"),
BANGLA("bn");
BANGLA("bn"),
BAKHTIARI("bqi-rIR");
companion object {
fun fromCode(code: String): Language {

View File

@@ -0,0 +1,18 @@
package com.v2ray.ang.dto
enum class NetworkType(val type: String) {
TCP("tcp"),
KCP("kcp"),
WS("ws"),
HTTP_UPGRADE("httpupgrade"),
SPLIT_HTTP("splithttp"),
XHTTP("xhttp"),
HTTP("http"),
H2("h2"),
//QUIC("quic"),
GRPC("grpc");
companion object {
fun fromString(type: String?) = entries.find { it.type == type } ?: TCP
}
}

View File

@@ -30,6 +30,8 @@ data class ProfileItem(
var mode: String? = null,
var serviceName: String? = null,
var authority: String? = null,
var xhttpMode: String? = null,
var xhttpExtra: String? = null,
var security: String? = null,
var sni: String? = null,
@@ -42,6 +44,7 @@ data class ProfileItem(
var spiderX: String? = null,
var secretKey: String? = null,
var preSharedKey: String? = null,
var localAddress: String? = null,
var reserved: String? = null,
var mtu: Int? = null,
@@ -66,10 +69,45 @@ data class ProfileItem(
return Utils.getIpv6Address(server) + ":" + serverPort
}
fun getKeyProperty(): ProfileItem {
val copy = this.copy()
copy.subscriptionId = ""
copy.addedTime = 0L
return copy
override fun equals(other: Any?): Boolean {
if (other == null) return false
val obj = other as ProfileItem
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
)
}
}

View File

@@ -195,6 +195,7 @@ data class V2rayConfig(
data class WireGuardBean(
var publicKey: String = "",
var preSharedKey: String = "",
var endpoint: String = ""
)
}
@@ -206,7 +207,7 @@ data class V2rayConfig(
var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null,
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
var splithttpSettings: SplithttpSettingsBean? = null,
var xhttpSettings: XhttpSettingsBean? = null,
var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null,
@@ -275,11 +276,11 @@ data class V2rayConfig(
val acceptProxyProtocol: Boolean? = null
)
data class SplithttpSettingsBean(
data class XhttpSettingsBean(
var path: String? = null,
var host: String? = null,
val maxUploadSize: Int? = null,
val maxConcurrentUploads: Int? = null
var mode: String? = null,
var extra: Any? = null,
)
data class HttpSettingsBean(
@@ -344,14 +345,21 @@ data class V2rayConfig(
}
fun populateTransportSettings(
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
transport: String,
headerType: String?,
host: String?,
path: String?,
seed: String?,
quicSecurity: String?,
key: String?,
mode: String?,
serviceName: String?,
authority: String?
): String? {
var sni: String? = null
network = transport
when (network) {
"tcp" -> {
NetworkType.TCP.type -> {
val tcpSetting = TcpSettingsBean()
if (headerType == AppConfig.HEADER_TYPE_HTTP) {
tcpSetting.header.type = AppConfig.HEADER_TYPE_HTTP
@@ -369,7 +377,7 @@ data class V2rayConfig(
tcpSettings = tcpSetting
}
"kcp" -> {
NetworkType.KCP.type -> {
val kcpsetting = KcpSettingsBean()
kcpsetting.header.type = headerType ?: "none"
if (seed.isNullOrEmpty()) {
@@ -380,7 +388,7 @@ data class V2rayConfig(
kcpSettings = kcpsetting
}
"ws" -> {
NetworkType.WS.type -> {
val wssetting = WsSettingsBean()
wssetting.headers.Host = host.orEmpty()
sni = wssetting.headers.Host
@@ -388,7 +396,7 @@ data class V2rayConfig(
wsSettings = wssetting
}
"httpupgrade" -> {
NetworkType.HTTP_UPGRADE.type -> {
val httpupgradeSetting = HttpupgradeSettingsBean()
httpupgradeSetting.host = host.orEmpty()
sni = httpupgradeSetting.host
@@ -396,16 +404,16 @@ data class V2rayConfig(
httpupgradeSettings = httpupgradeSetting
}
"splithttp" -> {
val splithttpSetting = SplithttpSettingsBean()
splithttpSetting.host = host.orEmpty()
sni = splithttpSetting.host
splithttpSetting.path = path ?: "/"
splithttpSettings = splithttpSetting
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
val xhttpSetting = XhttpSettingsBean()
xhttpSetting.host = host.orEmpty()
sni = xhttpSetting.host
xhttpSetting.path = path ?: "/"
xhttpSettings = xhttpSetting
}
"h2", "http" -> {
network = "h2"
NetworkType.H2.type, NetworkType.HTTP.type -> {
network = NetworkType.H2.type
val h2Setting = HttpSettingsBean()
h2Setting.host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
sni = h2Setting.host.getOrNull(0) ?: sni
@@ -413,15 +421,15 @@ data class V2rayConfig(
httpSettings = h2Setting
}
"quic" -> {
val quicsetting = QuicSettingBean()
quicsetting.security = quicSecurity ?: "none"
quicsetting.key = key.orEmpty()
quicsetting.header.type = headerType ?: "none"
quicSettings = quicsetting
}
// "quic" -> {
// val quicsetting = QuicSettingBean()
// quicsetting.security = quicSecurity ?: "none"
// quicsetting.key = key.orEmpty()
// quicsetting.header.type = headerType ?: "none"
// quicSettings = quicsetting
// }
"grpc" -> {
NetworkType.GRPC.type -> {
val grpcSetting = GrpcSettingsBean()
grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName.orEmpty()
@@ -436,8 +444,14 @@ data class V2rayConfig(
}
fun populateTlsSettings(
streamSecurity: String, allowInsecure: Boolean, sni: String?, fingerprint: String?, alpns: String?,
publicKey: String?, shortId: String?, spiderX: String?
streamSecurity: String,
allowInsecure: Boolean,
sni: String?,
fingerprint: String?,
alpns: String?,
publicKey: String?,
shortId: String?,
spiderX: String?
) {
security = streamSecurity
val tlsSetting = TlsSettingsBean(
@@ -470,16 +484,16 @@ data class V2rayConfig(
if (protocol.equals(EConfigType.VMESS.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)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.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)) {
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
return settings?.peers?.first()?.endpoint?.substringBeforeLast(":")
}
return null
}
@@ -488,16 +502,16 @@ data class V2rayConfig(
if (protocol.equals(EConfigType.VMESS.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)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.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)) {
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
return settings?.peers?.first()?.endpoint?.substringAfterLast(":")?.toInt()
}
return null
}
@@ -512,16 +526,16 @@ data class V2rayConfig(
if (protocol.equals(EConfigType.VMESS.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)
|| protocol.equals(EConfigType.TROJAN.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)
|| 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)) {
return settings?.secretKey
}
@@ -530,9 +544,9 @@ data class V2rayConfig(
fun getSecurityEncryption(): String? {
return when {
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.security
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.encryption
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.get(0)?.method
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.first()?.users?.first()?.security
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.first()?.users?.first()?.encryption
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.first()?.method
else -> null
}
}
@@ -545,7 +559,7 @@ data class V2rayConfig(
) {
val transport = streamSettings?.network ?: return null
return when (transport) {
"tcp" -> {
NetworkType.TCP.type -> {
val tcpSetting = streamSettings?.tcpSettings ?: return null
listOf(
tcpSetting.header.type,
@@ -554,7 +568,7 @@ data class V2rayConfig(
)
}
"kcp" -> {
NetworkType.KCP.type -> {
val kcpSetting = streamSettings?.kcpSettings ?: return null
listOf(
kcpSetting.header.type,
@@ -563,7 +577,7 @@ data class V2rayConfig(
)
}
"ws" -> {
NetworkType.WS.type -> {
val wsSetting = streamSettings?.wsSettings ?: return null
listOf(
"",
@@ -572,7 +586,7 @@ data class V2rayConfig(
)
}
"httpupgrade" -> {
NetworkType.HTTP_UPGRADE.type -> {
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
listOf(
"",
@@ -581,16 +595,16 @@ data class V2rayConfig(
)
}
"splithttp" -> {
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
val xhttpSettings = streamSettings?.xhttpSettings ?: return null
listOf(
"",
splithttpSetting.host,
splithttpSetting.path
xhttpSettings.host,
xhttpSettings.path
)
}
"h2" -> {
NetworkType.H2.type -> {
val h2Setting = streamSettings?.httpSettings ?: return null
listOf(
"",
@@ -599,16 +613,16 @@ data class V2rayConfig(
)
}
"quic" -> {
val quicSetting = streamSettings?.quicSettings ?: return null
listOf(
quicSetting.header.type,
quicSetting.security,
quicSetting.key
)
}
// "quic" -> {
// val quicSetting = streamSettings?.quicSettings ?: return null
// listOf(
// quicSetting.header.type,
// quicSetting.security,
// quicSetting.key
// )
// }
"grpc" -> {
NetworkType.GRPC.type -> {
val grpcSetting = streamSettings?.grpcSettings ?: return null
listOf(
if (grpcSetting.multiMode == true) "multi" else "gun",

View File

@@ -1,6 +1,6 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.util.Utils
@@ -29,9 +29,40 @@ open class FmtBase {
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
}
fun getItemFormQuery(config: ProfileItem, queryParam: Map<String, String>, allowInsecure: Boolean) {
config.network = queryParam["type"] ?: NetworkType.TCP.type
//TODO
if (config.network == NetworkType.SPLIT_HTTP.type) config.network = NetworkType.XHTTP.type
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.xhttpMode = queryParam["mode"]
config.xhttpExtra = queryParam["extra"]
config.security = queryParam["security"]
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.flow = queryParam["flow"]
}
fun getQueryDic(config: ProfileItem): HashMap<String, String> {
val dicQuery = HashMap<String, String>()
dicQuery["security"] = config.security?.ifEmpty { "none" }.orEmpty()
config.sni.let { if (it.isNotNullEmpty()) dicQuery["sni"] = it.orEmpty() }
config.alpn.let { if (it.isNotNullEmpty()) dicQuery["alpn"] = it.orEmpty() }
@@ -41,37 +72,45 @@ open class FmtBase {
config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() }
config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
dicQuery["type"] = config.network?.ifEmpty { AppConfig.DEFAULT_NETWORK }.orEmpty()
val networkType = NetworkType.fromString(config.network)
dicQuery["type"] = networkType.type
when (config.network) {
"tcp" -> {
when (networkType) {
NetworkType.TCP -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
}
"kcp" -> {
NetworkType.KCP -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.seed.let { if (it.isNotNullEmpty()) dicQuery["seed"] = it.orEmpty() }
}
"ws", "httpupgrade", "splithttp" -> {
NetworkType.WS, NetworkType.HTTP_UPGRADE -> {
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
}
"http", "h2" -> {
NetworkType.SPLIT_HTTP, NetworkType.XHTTP -> {
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
config.xhttpMode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
config.xhttpExtra.let { if (it.isNotNullEmpty()) dicQuery["extra"] = it.orEmpty() }
}
NetworkType.HTTP, NetworkType.H2 -> {
dicQuery["type"] = "http"
config.host.let { if (it.isNotNullEmpty()) dicQuery["host"] = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) dicQuery["path"] = it.orEmpty() }
}
"quic" -> {
dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
config.quicSecurity.let { if (it.isNotNullEmpty()) dicQuery["quicSecurity"] = it.orEmpty() }
config.quicKey.let { if (it.isNotNullEmpty()) dicQuery["key"] = it.orEmpty() }
}
// NetworkType.QUIC -> {
// dicQuery["headerType"] = config.headerType?.ifEmpty { "none" }.orEmpty()
// config.quicSecurity.let { if (it.isNotNullEmpty()) dicQuery["quicSecurity"] = it.orEmpty() }
// config.quicKey.let { if (it.isNotNullEmpty()) dicQuery["key"] = it.orEmpty() }
// }
"grpc" -> {
NetworkType.GRPC -> {
config.mode.let { if (it.isNotNullEmpty()) dicQuery["mode"] = it.orEmpty() }
config.authority.let { if (it.isNotNullEmpty()) dicQuery["authority"] = it.orEmpty() }
config.serviceName.let { if (it.isNotNullEmpty()) dicQuery["serviceName"] = it.orEmpty() }

View File

@@ -10,7 +10,7 @@ object HttpFmt : FmtBase() {
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
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.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {

View File

@@ -0,0 +1,109 @@
package com.v2ray.ang.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt : FmtBase() {
fun parse(str: String): ProfileItem? {
return parseSip002(str) ?: parseLegacy(str)
}
fun parseSip002(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.SHADOWSOCKS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.idnHost.isEmpty()) return null
if (uri.port <= 0) return null
if (uri.userInfo.isNullOrEmpty()) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
val result = if (uri.userInfo.contains(":")) {
uri.userInfo.split(":", limit = 2)
} else {
Utils.decode(uri.userInfo).split(":", limit = 2)
}
if (result.count() == 2) {
config.method = result.first()
config.password = result.last()
}
if (!uri.rawQuery.isNullOrEmpty()) {
val queryParam = getQueryParam(uri)
if (queryParam["plugin"] == "obfs-local" && queryParam["obfs"] == "http") {
config.network = NetworkType.TCP.type
config.headerType = "http"
config.host = queryParam["obfs-host"]
config.path = queryParam["path"]
}
}
return config
}
fun parseLegacy(str: String): ProfileItem? {
val config = ProfileItem.create(EConfigType.SHADOWSOCKS)
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result) ?: return null
config.server = match.groupValues[3].removeSurrounding("[", "]")
config.serverPort = match.groupValues[4]
config.password = match.groupValues[2]
config.method = match.groupValues[1].lowercase()
return config
}
fun toUri(config: ProfileItem): String {
val pw = "${config.method}:${config.password}"
return toUri(config, Utils.encode(pw), null)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.method = profileItem.method
}
return outboundBean
}
}

View File

@@ -15,13 +15,14 @@ object SocksFmt : FmtBase() {
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.idnHost.isEmpty()) return null
if (uri.port <= 0) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
config.server = uri.idnHost
config.serverPort = uri.port.toString()
if (uri.userInfo?.isEmpty() == false) {
val result = Utils.decode(uri.userInfo).split(":")
val result = Utils.decode(uri.userInfo).split(":", limit = 2)
if (result.count() == 2) {
config.username = result.first()
config.password = result.last()
@@ -38,13 +39,13 @@ object SocksFmt : FmtBase() {
else
":"
return toUri(config, pw, null)
return toUri(config, Utils.encode(pw), null)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
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.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {

View File

@@ -2,6 +2,7 @@ package com.v2ray.ang.fmt
import com.v2ray.ang.AppConfig
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
@@ -22,37 +23,14 @@ object TrojanFmt : FmtBase() {
config.password = uri.userInfo
if (uri.rawQuery.isNullOrEmpty()) {
config.network = NetworkType.TCP.type
config.security = AppConfig.TLS
config.insecure = allowInsecure
} else {
val queryParam = getQueryParam(uri)
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.flow = queryParam["flow"]
getItemFormQuery(config, queryParam, allowInsecure)
config.security = queryParam["security"] ?: AppConfig.TLS
}
return config
@@ -67,7 +45,7 @@ object TrojanFmt : FmtBase() {
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
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.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password

View File

@@ -6,6 +6,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import java.net.URI
@@ -25,31 +26,7 @@ object VlessFmt : FmtBase() {
config.password = uri.userInfo
config.method = queryParam["encryption"] ?: "none"
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.flow = queryParam["flow"]
getItemFormQuery(config, queryParam, allowInsecure)
return config
}
@@ -65,7 +42,7 @@ object VlessFmt : FmtBase() {
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
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.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
@@ -85,6 +62,8 @@ object VlessFmt : FmtBase() {
profileItem.serviceName,
profileItem.authority,
)
outboundBean?.streamSettings?.xhttpSettings?.mode = profileItem.xhttpMode
outboundBean?.streamSettings?.xhttpSettings?.extra = JsonUtil.parseString(profileItem.xhttpExtra)
outboundBean?.streamSettings?.populateTlsSettings(
profileItem.security.orEmpty(),

View File

@@ -4,6 +4,7 @@ import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
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.dto.VmessQRCode
@@ -47,27 +48,29 @@ object VmessFmt : FmtBase() {
config.password = vmessQRCode.id
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.host = vmessQRCode.host
config.path = vmessQRCode.path
when (config.network) {
"kcp" -> {
when (NetworkType.fromString(config.network)) {
NetworkType.KCP -> {
config.seed = vmessQRCode.path
}
"quic" -> {
config.quicSecurity = vmessQRCode.host
config.quicKey = vmessQRCode.path
}
// NetworkType.QUIC -> {
// config.quicSecurity = vmessQRCode.host
// config.quicKey = vmessQRCode.path
// }
"grpc" -> {
NetworkType.GRPC -> {
config.mode = vmessQRCode.type
config.serviceName = vmessQRCode.path
config.authority = vmessQRCode.host
}
else -> {}
}
config.security = vmessQRCode.tls
config.insecure = allowInsecure
config.sni = vmessQRCode.sni
@@ -90,22 +93,24 @@ object VmessFmt : FmtBase() {
vmessQRCode.net = config.network.orEmpty()
vmessQRCode.type = config.headerType.orEmpty()
when (config.network) {
"kcp" -> {
when (NetworkType.fromString(config.network)) {
NetworkType.KCP -> {
vmessQRCode.path = config.seed.orEmpty()
}
"quic" -> {
vmessQRCode.host = config.quicSecurity.orEmpty()
vmessQRCode.path = config.quicKey.orEmpty()
}
// NetworkType.QUIC -> {
// vmessQRCode.host = config.quicSecurity.orEmpty()
// vmessQRCode.path = config.quicKey.orEmpty()
// }
"grpc" -> {
NetworkType.GRPC -> {
vmessQRCode.type = config.mode.orEmpty()
vmessQRCode.path = config.serviceName.orEmpty()
vmessQRCode.host = config.authority.orEmpty()
}
else -> {}
}
config.host.let { if (it.isNotNullEmpty()) vmessQRCode.host = it.orEmpty() }
config.path.let { if (it.isNotNullEmpty()) vmessQRCode.path = it.orEmpty() }
@@ -119,7 +124,7 @@ object VmessFmt : FmtBase() {
}
fun parseVmessStd(str: String): ProfileItem? {
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.VMESS)
val uri = URI(Utils.fixIllegalUrl(str))
@@ -132,23 +137,7 @@ object VmessFmt : FmtBase() {
config.password = uri.userInfo
config.method = AppConfig.DEFAULT_SECURITY
config.network = queryParam["type"] ?: "tcp"
config.headerType = queryParam["headerType"]
config.host = queryParam["host"]
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
config.serviceName = queryParam["serviceName"]
config.authority = queryParam["authority"]
config.security = queryParam["security"]
config.insecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"]
getItemFormQuery(config, queryParam, allowInsecure)
return config
}
@@ -157,7 +146,7 @@ object VmessFmt : FmtBase() {
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
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.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()

View File

@@ -22,9 +22,10 @@ object WireguardFmt : FmtBase() {
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.secretKey = uri.userInfo
config.secretKey = uri.userInfo.orEmpty()
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
config.publicKey = queryParam["publickey"].orEmpty()
config.preSharedKey = queryParam["presharedkey"].orEmpty()
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
config.reserved = (queryParam["reserved"] ?: "0,0,0")
@@ -33,33 +34,75 @@ object WireguardFmt : FmtBase() {
fun parseWireguardConfFile(str: String): ProfileItem? {
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
str.lines().forEach { line ->
val trimmedLine = line.trim()
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) {
return@forEach
}
when {
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
trimmedLine.isBlank() || trimmedLine.startsWith("#") -> Unit // Skip blank lines or comments
currentSection != null -> {
val (key, value) = trimmedLine.split("=").map { it.trim() }
queryParam[key.lowercase()] = value // Store the key in lowercase for case-insensitivity
else -> {
if (currentSection != null) {
val parts = trimmedLine.split("=", limit = 2).map { it.trim() }
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.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
config.publicKey = queryParam["publickey"].orEmpty()
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
config.reserved = (queryParam["reserved"] ?: "0,0,0")
config.secretKey = interfaceParams["privatekey"].orEmpty()
config.remarks = System.currentTimeMillis().toString()
config.localAddress = interfaceParams["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
config.mtu = Utils.parseInt(interfaceParams["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
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
}
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 {
val dicQuery = HashMap<String, String>()
@@ -72,24 +115,10 @@ object WireguardFmt : FmtBase() {
if (config.mtu != null) {
dicQuery["mtu"] = config.mtu.toString()
}
if (config.preSharedKey != null) {
dicQuery["presharedkey"] = Utils.removeWhiteSpace(config.preSharedKey).orEmpty()
}
return toUri(config, config.secretKey, dicQuery)
}
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
val outboundBean = OutboundBean.create(EConfigType.WIREGUARD)
outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = profileItem.secretKey
wireguard.address = (profileItem.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4).split(",")
wireguard.peers?.get(0)?.publicKey = profileItem.publicKey.orEmpty()
wireguard.peers?.get(0)?.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
wireguard.mtu = profileItem.mtu?.toInt()
wireguard.reserved = profileItem.reserved?.split(",")?.map { it.toInt() }
}
return outboundBean
}
}
}

View File

@@ -277,10 +277,10 @@ object AngConfigManager {
if (serverList.isNotEmpty()) {
var count = 0
for (srv in serverList.reversed()) {
val config = CustomFmt.parse(server) ?: continue
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: continue
config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv)?:"")
count += 1
}
return count

View File

@@ -5,6 +5,7 @@ import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
@@ -19,7 +20,7 @@ object MigrateManager {
if (serverStorage.count().toInt() == 0) {
return false
}
var serverList = serverStorage.allKeys() ?: return false
val serverList = serverStorage.allKeys() ?: return false
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + serverList.count())
for (guid in serverList) {
@@ -36,7 +37,7 @@ object MigrateManager {
//check and remove old
decodeServerConfig(guid) ?: continue
//serverStorage.remove(guid)
serverStorage.remove(guid)
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + config.remarks)
}
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-end")
@@ -70,10 +71,10 @@ object MigrateManager {
config.serverPort = outbound.getServerPort().toString()
config.method = outbound.getSecurityEncryption()
config.password = outbound.getPassword()
config.flow = outbound?.settings?.vnext?.get(0)?.users[0]?.flow ?: outbound?.settings?.servers?.get(0)?.flow
config.flow = outbound?.settings?.vnext?.first()?.users?.first()?.flow ?: outbound?.settings?.servers?.first()?.flow
config.network = outbound?.streamSettings?.network ?: NetworkType.TCP.type
outbound.getTransportSettingDetails()?.let { transportDetails ->
config.network = "tcp"
config.headerType = transportDetails[0].orEmpty()
config.host = transportDetails[1].orEmpty()
config.path = transportDetails[2].orEmpty()
@@ -107,7 +108,7 @@ object MigrateManager {
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
config.username = outbound.settings?.servers?.first()?.users?.first()?.user
config.password = outbound.getPassword()
return config
@@ -120,7 +121,7 @@ object MigrateManager {
config.remarks = configOld.remarks
config.server = outbound.getServerAddress()
config.serverPort = outbound.getServerPort().toString()
config.username = outbound.settings?.servers?.get(0)?.users?.get(0)?.user
config.username = outbound.settings?.servers?.first()?.users?.first()?.user
config.password = outbound.getPassword()
return config

View File

@@ -336,7 +336,7 @@ object MmkvManager {
}
fun decodeSettingsBool(key: String): Boolean {
return settingsStorage.decodeBool(key)
return settingsStorage.decodeBool(key,false)
}
fun decodeSettingsBool(key: String, defaultValue: Boolean): Boolean {

View File

@@ -43,12 +43,12 @@ object SettingsManager {
}
fun resetRoutingRulesets(context: Context, index: Int) {
fun resetRoutingRulesetsFromPresets(context: Context, index: Int) {
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
resetRoutingRulesetsCommon(rulesetList)
}
fun resetRoutingRulesetsFromClipboard(content: String?): Boolean {
fun resetRoutingRulesets(content: String?): Boolean {
if (content.isNullOrEmpty()) {
return false
}
@@ -95,7 +95,7 @@ object SettingsManager {
if (rulesetList.isNullOrEmpty()) return
if (index < 0 || index >= rulesetList.count()) {
rulesetList.add(ruleset)
rulesetList.add(0, ruleset)
} else {
rulesetList[index] = ruleset
}

View File

@@ -8,12 +8,16 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.DEFAULT_NETWORK
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
import com.v2ray.ang.AppConfig.DNS_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_DOMAIN
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_DOMAIN
import com.v2ray.ang.AppConfig.DNS_PUB_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_PUB_DOMAIN
import com.v2ray.ang.AppConfig.DNS_QUAD9_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_QUAD9_DOMAIN
import com.v2ray.ang.AppConfig.DNS_YANDEX_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_YANDEX_DOMAIN
import com.v2ray.ang.AppConfig.GEOIP_CN
import com.v2ray.ang.AppConfig.GEOSITE_CN
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
@@ -57,7 +61,7 @@ object V2rayConfigManager {
}
val result = getV2rayNonCustomConfig(context, config)
Log.d(ANG_PACKAGE, result.content)
//Log.d(ANG_PACKAGE, result.content)
result.guid = guid
return result
} catch (e: Exception) {
@@ -376,10 +380,12 @@ object V2rayConfigManager {
hosts[GOOGLEAPIS_CN_DOMAIN] = GOOGLEAPIS_COM_DOMAIN
// 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_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_QUAD9_DOMAIN] = DNS_QUAD9_ADDRESSES
hosts[DNS_YANDEX_DOMAIN] = DNS_YANDEX_ADDRESSES
// DNS dns对象
@@ -419,7 +425,7 @@ object V2rayConfigManager {
) {
muxEnabled = false
} 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
}
@@ -455,7 +461,7 @@ object V2rayConfigManager {
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
val requestString: String by lazy {
"""{"version":"1.1","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.122 Mobile Safari/537.36"Safari/537.36,"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.122 Mobile Safari/537.36"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
}
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
requestString,

View File

@@ -13,46 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.helper;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
package com.v2ray.ang.helper
/**
* 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)
*/
public interface ItemTouchHelperAdapter {
interface ItemTouchHelperAdapter {
/**
* 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/>
* <br/>
* Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
* an item is shifted, and **not** at the end of a "drop" event.<br></br>
* <br></br>
* Implementations should call [RecyclerView.Adapter.notifyItemMoved] after
* adjusting the underlying data to reflect this move.
*
* @param fromPosition The start 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.
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
* @see RecyclerView.getAdapterPositionFor
* @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/>
* <br/>
* Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after
* Called when an item has been dismissed by a swipe.<br></br>
* <br></br>
* Implementations should call [RecyclerView.Adapter.notifyItemRemoved] after
* adjusting the underlying data to reflect this removal.
*
* @param position The position of the item dismissed.
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
* @see RecyclerView.getAdapterPositionFor
* @see RecyclerView.ViewHolder.getAdapterPosition
*/
void onItemDismiss(int position);
fun onItemDismiss(position: Int)
}

View File

@@ -13,29 +13,26 @@
* See the License for the specific language governing permissions and
* 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
* ItemTouchHelper.Callback}.
* Interface to notify an item ViewHolder of relevant callbacks from [ ].
*
* @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.
*/
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.
*/
void onItemClear();
fun onItemClear()
}

View File

@@ -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; // 增加滑动逃逸速度,使得更难触发滑动
}
}

View File

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

View File

@@ -0,0 +1,18 @@
package com.v2ray.ang.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.V2RayServiceManager
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//Check if context is not null and action is the one we want
if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) return
//Check if flag is true and a server is selected
if (!MmkvManager.decodeStartOnBoot() || MmkvManager.getSelectServer().isNullOrEmpty()) return
//Start v2ray
V2RayServiceManager.startV2Ray(context)
}
}

View File

@@ -16,9 +16,9 @@ class TaskerReceiver : BroadcastReceiver() {
try {
val bundle = intent?.getBundleExtra(AppConfig.TASKER_EXTRA_BUNDLE)
val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false)
val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "")
val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID).orEmpty()
if (switch == null || guid == null || TextUtils.isEmpty(guid)) {
if (switch == null || TextUtils.isEmpty(guid)) {
return
} else if (switch) {
if (guid == AppConfig.TASKER_DEFAULT_GUID) {

View File

@@ -8,11 +8,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ProcessService {
private val TAG = ANG_PACKAGE
private var process: Process? = null
fun runProcess(context: Context, cmd: MutableList<String>) {
Log.d(TAG, cmd.toString())
Log.d(ANG_PACKAGE, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
@@ -23,23 +22,23 @@ class ProcessService {
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(50L)
Log.d(TAG, "runProcess check")
Log.d(ANG_PACKAGE, "runProcess check")
process?.waitFor()
Log.d(TAG, "runProcess exited")
Log.d(ANG_PACKAGE, "runProcess exited")
}
Log.d(TAG, process.toString())
Log.d(ANG_PACKAGE, process.toString())
} catch (e: Exception) {
Log.d(TAG, e.toString())
Log.d(ANG_PACKAGE, e.toString())
}
}
fun stopProcess() {
try {
Log.d(TAG, "runProcess destroy")
Log.d(ANG_PACKAGE, "runProcess destroy")
process?.destroy()
} catch (e: Exception) {
Log.d(TAG, e.toString())
Log.d(ANG_PACKAGE, e.toString())
}
}
}

View File

@@ -9,6 +9,7 @@ import android.graphics.drawable.Icon
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.util.MessageUtil
@@ -32,24 +33,31 @@ class QSTileService : TileService() {
qsTile?.updateTile()
}
/**
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
*/
override fun onStartListening() {
super.onStartListening()
setState(Tile.STATE_INACTIVE)
mMsgReceive = ReceiveMessageHandler(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), RECEIVER_EXPORTED)
} else {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
}
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onStopListening() {
super.onStopListening()
unregisterReceiver(mMsgReceive)
mMsgReceive = null
try {
applicationContext.unregisterReceiver(mMsgReceive)
mMsgReceive = null
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onClick() {

View File

@@ -13,6 +13,7 @@ import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT
@@ -126,6 +127,11 @@ object V2RayServiceManager {
}
}
/**
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
*/
fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
val guid = MmkvManager.getSelectServer() ?: return
@@ -142,11 +148,7 @@ object V2RayServiceManager {
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
} else {
service.registerReceiver(mMsgReceive, mFilter)
}
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
@@ -156,7 +158,7 @@ object V2RayServiceManager {
currentConfig = config
try {
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true)
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}

View File

@@ -24,7 +24,7 @@ import libv2ray.Libv2ray
import java.util.concurrent.Executors
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() {
super.onCreate()

View File

@@ -17,6 +17,7 @@ import android.os.StrictMode
import android.util.Log
import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
@@ -153,9 +154,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
val selfPackageName = BuildConfig.APPLICATION_ID
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)) {
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS) == true
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
//process self package
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
apps?.forEach {
@@ -165,6 +166,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
else
builder.addAllowedApplication(it)
} catch (e: PackageManager.NameNotFoundException) {
Log.d(ANG_PACKAGE, "setup error : --${e.localizedMessage}")
}
}
} else {
@@ -215,11 +217,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
"--loglevel", "notice"
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw")
cmd.add("$LOOPBACK:${localDnsPort}")
@@ -232,7 +234,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
process = proBuilder
.directory(applicationContext.filesDir)
.start()
Thread(Runnable {
Thread {
Log.d(packageName, "$TUN2SOCKS check")
process.waitFor()
Log.d(packageName, "$TUN2SOCKS exited")
@@ -240,7 +242,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
Log.d(packageName, "$TUN2SOCKS restart")
runTun2socks()
}
}).start()
}.start()
Log.d(packageName, process.toString())
sendFd()

View File

@@ -133,7 +133,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.request(Manifest.permission.POST_NOTIFICATIONS)
.subscribe {
if (!it)
toast(R.string.toast_permission_denied)
toast(R.string.toast_permission_denied_notification)
}
}

View File

@@ -1,15 +1,18 @@
package com.v2ray.ang.ui
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
@@ -83,13 +86,13 @@ class RoutingSettingActivity : BaseActivity() {
true
}
R.id.import_rulesets -> {
R.id.import_predefined_rulesets -> {
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
.setPositiveButton(android.R.string.ok) { _, _ ->
AlertDialog.Builder(this).setItems(preset_rulesets.asList().toTypedArray()) { _, i ->
try {
lifecycleScope.launch(Dispatchers.IO) {
SettingsManager.resetRoutingRulesets(this@RoutingSettingActivity, i)
SettingsManager.resetRoutingRulesetsFromPresets(this@RoutingSettingActivity, i)
launch(Dispatchers.Main) {
refreshData()
toast(R.string.toast_success)
@@ -120,7 +123,7 @@ class RoutingSettingActivity : BaseActivity() {
return@setPositiveButton
}
lifecycleScope.launch(Dispatchers.IO) {
val result = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
val result = SettingsManager.resetRoutingRulesets(clipboard)
withContext(Dispatchers.Main) {
if (result) {
refreshData()
@@ -138,6 +141,18 @@ class RoutingSettingActivity : BaseActivity() {
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 -> {
val rulesetList = MmkvManager.decodeRoutingRulesets()
@@ -153,6 +168,34 @@ class RoutingSettingActivity : BaseActivity() {
else -> super.onOptionsItemSelected(item)
}
private val scanQRcodeForRulesets = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importRulesetsFromQRcode(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private fun importRulesetsFromQRcode(qrcode: String?): Boolean {
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
val result = SettingsManager.resetRoutingRulesets(qrcode)
withContext(Dispatchers.Main) {
if (result) {
refreshData()
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do nothing
}
.show()
return true
}
fun refreshData() {
rulesets.clear()
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())

View File

@@ -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.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
@@ -79,6 +81,10 @@ class ServerActivity : BaseActivity() {
private val alpns: Array<out String> by lazy {
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.
// 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 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_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 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) }
@@ -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_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_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?) {
@@ -142,7 +151,7 @@ class ServerActivity : BaseActivity() {
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
val types = transportTypes(networks[position])
sp_header_type?.isEnabled = types.size > 1
@@ -150,28 +159,49 @@ class ServerActivity : BaseActivity() {
ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sp_header_type?.adapter = adapter
sp_header_type_title?.text = if (networks[position] == "grpc")
getString(R.string.server_lab_mode_type) else
getString(R.string.server_lab_head_type)
config?.headerType?.let { it ->
sp_header_type?.setSelection(Utils.arrayFind(types, it))
}
config?.host?.let { it ->
et_request_host?.text = Utils.getEditable(it)
}
config?.path?.let { it ->
et_path?.text = Utils.getEditable(it)
}
sp_header_type_title?.text =
when (networks[position]) {
NetworkType.GRPC.type -> getString(R.string.server_lab_mode_type)
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> getString(R.string.server_lab_xhttp_mode)
else -> getString(R.string.server_lab_head_type)
}.orEmpty()
sp_header_type?.setSelection(
Utils.arrayFind(
types,
when (networks[position]) {
NetworkType.GRPC.type -> config?.mode
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> config?.xhttpMode
else -> config?.headerType
}.orEmpty()
)
)
et_request_host?.text = Utils.getEditable(
when (networks[position]) {
//"quic" -> config?.quicSecurity
NetworkType.GRPC.type -> config?.authority
else -> config?.host
}.orEmpty()
)
et_path?.text = Utils.getEditable(
when (networks[position]) {
NetworkType.KCP.type -> config?.seed
//"quic" -> config?.quicKey
NetworkType.GRPC.type -> config?.serviceName
else -> config?.path
}.orEmpty()
)
tv_request_host?.text = Utils.getEditable(
getString(
when (networks[position]) {
"tcp" -> R.string.server_lab_request_host_http
"ws" -> R.string.server_lab_request_host_ws
"httpupgrade" -> R.string.server_lab_request_host_httpupgrade
"splithttp" -> R.string.server_lab_request_host_splithttp
"h2" -> R.string.server_lab_request_host_h2
"quic" -> R.string.server_lab_request_host_quic
"grpc" -> R.string.server_lab_request_host_grpc
NetworkType.TCP.type -> R.string.server_lab_request_host_http
NetworkType.WS.type -> R.string.server_lab_request_host_ws
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_request_host_httpupgrade
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> R.string.server_lab_request_host_xhttp
NetworkType.H2.type -> R.string.server_lab_request_host_h2
//"quic" -> R.string.server_lab_request_host_quic
NetworkType.GRPC.type -> R.string.server_lab_request_host_grpc
else -> R.string.server_lab_request_host
}
)
@@ -180,17 +210,29 @@ class ServerActivity : BaseActivity() {
tv_path?.text = Utils.getEditable(
getString(
when (networks[position]) {
"kcp" -> R.string.server_lab_path_kcp
"ws" -> R.string.server_lab_path_ws
"httpupgrade" -> R.string.server_lab_path_httpupgrade
"splithttp" -> R.string.server_lab_path_splithttp
"h2" -> R.string.server_lab_path_h2
"quic" -> R.string.server_lab_path_quic
"grpc" -> R.string.server_lab_path_grpc
NetworkType.KCP.type -> R.string.server_lab_path_kcp
NetworkType.WS.type -> R.string.server_lab_path_ws
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_path_httpupgrade
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> R.string.server_lab_path_xhttp
NetworkType.H2.type -> R.string.server_lab_path_h2
//"quic" -> R.string.server_lab_path_quic
NetworkType.GRPC.type -> R.string.server_lab_path_grpc
else -> R.string.server_lab_path
}
)
)
et_extra?.text = Utils.getEditable(
when (networks[position]) {
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> config?.xhttpExtra
else -> null
}.orEmpty()
)
layout_extra?.visibility =
when (networks[position]) {
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> View.VISIBLE
else -> View.GONE
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
@@ -202,7 +244,7 @@ class ServerActivity : BaseActivity() {
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
val isBlank = streamSecuritys[position].isBlank()
val isTLS = streamSecuritys[position] == TLS
@@ -280,29 +322,21 @@ class ServerActivity : BaseActivity() {
} else if (config.configType == EConfigType.WIREGUARD) {
et_id.text = Utils.getEditable(config.secretKey.orEmpty())
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
if (config.reserved == null) {
et_reserved1?.text = Utils.getEditable("0,0,0")
} else {
et_reserved1?.text = Utils.getEditable(config.reserved?.toString())
}
if (config.localAddress == null) {
et_local_address?.text = Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
} 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())
}
et_preshared_key?.visibility = View.VISIBLE
et_preshared_key?.text = Utils.getEditable(config.preSharedKey.orEmpty())
et_reserved1?.text = Utils.getEditable(config.reserved ?: "0,0,0")
et_local_address?.text = Utils.getEditable(
config.localAddress ?: "$WIREGUARD_LOCAL_ADDRESS_V4,$WIREGUARD_LOCAL_ADDRESS_V6"
)
et_local_mtu?.text = Utils.getEditable(config.mtu?.toString() ?: WIREGUARD_LOCAL_MTU)
} else if (config.configType == EConfigType.HYSTERIA2) {
et_obfs_password?.text = Utils.getEditable(config.obfsPassword)
et_port_hop?.text = Utils.getEditable(config.portHopping)
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
}
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val securityEncryptions =
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security = Utils.arrayFind(securityEncryptions, config.method.orEmpty())
if (security >= 0) {
sp_security?.setSelection(security)
@@ -333,7 +367,7 @@ class ServerActivity : BaseActivity() {
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
} else if (config.security == REALITY) { // reality settings
} else if (config.security == REALITY) {
container_public_key?.visibility = View.VISIBLE
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
container_short_id?.visibility = View.VISIBLE
@@ -353,7 +387,6 @@ class ServerActivity : BaseActivity() {
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
}
val network = Utils.arrayFind(networks, config.network.orEmpty())
if (network >= 0) {
sp_network?.setSelection(network)
@@ -407,7 +440,8 @@ class ServerActivity : BaseActivity() {
return false
}
}
val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(createConfigType)
val config =
MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(createConfigType)
if (config.configType != EConfigType.SOCKS
&& config.configType != EConfigType.HTTP
&& TextUtils.isEmpty(et_id.text.toString())
@@ -428,6 +462,12 @@ class ServerActivity : BaseActivity() {
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)
saveStreamSettings(config)
@@ -436,7 +476,7 @@ class ServerActivity : BaseActivity() {
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId.orEmpty()
}
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config))
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config) ?: "")
MmkvManager.encodeServerConfig(editGuid, config)
toast(R.string.toast_success)
finish()
@@ -464,6 +504,7 @@ class ServerActivity : BaseActivity() {
} else if (config.configType == EConfigType.WIREGUARD) {
config.secretKey = et_id.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.localAddress = et_local_address?.text.toString().trim()
config.mtu = Utils.parseInt(et_local_mtu?.text.toString())
@@ -492,6 +533,8 @@ class ServerActivity : BaseActivity() {
profileItem.mode = transportTypes(networks[network])[type]
profileItem.serviceName = path
profileItem.authority = requestHost
profileItem.xhttpMode = transportTypes(networks[network])[type]
profileItem.xhttpExtra = et_extra?.text?.toString()?.trim()
}
private fun saveTls(config: ProfileItem) {
@@ -506,7 +549,7 @@ class ServerActivity : BaseActivity() {
val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE) == true
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE)
} else {
allowinsecures[allowInsecureField].toBoolean()
}
@@ -523,18 +566,22 @@ class ServerActivity : BaseActivity() {
private fun transportTypes(network: String?): Array<out String> {
return when (network) {
"tcp" -> {
NetworkType.TCP.type -> {
tcpTypes
}
"kcp", "quic" -> {
NetworkType.KCP.type -> {
kcpAndQuicTypes
}
"grpc" -> {
NetworkType.GRPC.type -> {
grpcModes
}
NetworkType.SPLIT_HTTP.type, NetworkType.XHTTP.type -> {
xhttpMode
}
else -> {
arrayOf("---")
}

View File

@@ -12,10 +12,9 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import me.drakeet.support.toast.ToastCompat
@@ -75,8 +74,8 @@ class ServerCustomConfigActivity : BaseActivity() {
return false
}
val v2rayConfig = try {
JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
val profileItem = try {
CustomFmt.parse(binding.editor.text.toString())
} catch (e: Exception) {
e.printStackTrace()
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)
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.encodeServerRaw(editGuid, binding.editor.text.toString())

View File

@@ -75,6 +75,7 @@ class UserAssetActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.add_file -> showFileChooser().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 }
else -> super.onOptionsItemSelected(item)
}
@@ -156,6 +157,40 @@ class UserAssetActivity : BaseActivity() {
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() {
val dialog = AlertDialog.Builder(this)
.setView(LayoutProgressBinding.inflate(layoutInflater).root)

View File

@@ -14,6 +14,11 @@ import com.v2ray.ang.util.Utils
import java.io.File
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) }
var del_config: MenuItem? = null
@@ -28,10 +33,15 @@ class UserAssetUrlActivity : BaseActivity() {
title = getString(R.string.title_user_asset_add_url)
val assetItem = MmkvManager.decodeAsset(editAssetId)
if (assetItem != null) {
bindingAsset(assetItem)
} else {
clearAsset()
val assetUrlQrcode = intent.getStringExtra(ASSET_URL_QRCODE)
val assetNameQrcode = File(assetUrlQrcode.toString()).name
when {
assetItem != null -> bindingAsset(assetItem)
assetUrlQrcode != null -> {
binding.etRemarks.setText(assetNameQrcode)
binding.etUrl.setText(assetUrlQrcode)
}
else -> clearAsset()
}
}

View File

@@ -13,13 +13,11 @@ object AppManagerUtil {
val apps = ArrayList<AppInfo>()
for (pkg in packages) {
//if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
val applicationInfo = pkg.applicationInfo
val applicationInfo = pkg.applicationInfo ?: continue
val appName = applicationInfo.loadLabel(packageManager).toString()
val appIcon = applicationInfo.loadIcon(packageManager)
val isSystemApp = applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM > 0
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
apps.add(appInfo)

View File

@@ -2,6 +2,8 @@ package com.v2ray.ang.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
@@ -15,11 +17,13 @@ object JsonUtil {
return gson.toJson(src)
}
fun <T> fromJson(json: String, cls: Class<T>): T {
return gson.fromJson(json, cls)
fun <T> fromJson(src: String, cls: Class<T>): T {
return gson.fromJson(src, cls)
}
fun toJsonPretty(src: Any?): String {
fun toJsonPretty(src: Any?): String? {
if (src == null)
return null
val gsonPre = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
@@ -34,4 +38,15 @@ object JsonUtil {
.create()
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
}
}
}

View File

@@ -17,6 +17,7 @@ import android.util.Log
import android.util.Patterns
import android.webkit.URLUtil
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.LOOPBACK
@@ -130,7 +131,8 @@ object Utils {
* get remote dns servers from preference
*/
fun getRemoteDnsServers(): List<String> {
val remoteDns = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_PROXY
val remoteDns =
MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_PROXY
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_PROXY)
@@ -148,7 +150,8 @@ object Utils {
* get remote dns servers from preference
*/
fun getDomesticDnsServers(): List<String> {
val domesticDns = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val domesticDns =
MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_DIRECT)
@@ -292,7 +295,7 @@ object Utils {
fun urlEncode(url: String): String {
return try {
URLEncoder.encode(url, Charsets.UTF_8.toString())
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
} catch (e: Exception) {
e.printStackTrace()
url
@@ -356,7 +359,11 @@ object Utils {
}
@Throws(IOException::class)
fun getUrlContentWithCustomUserAgent(urlStr: String?, timeout: Int = 30000, httpPort: Int = 0): String {
fun getUrlContentWithCustomUserAgent(
urlStr: String?,
timeout: Int = 30000,
httpPort: Int = 0
): String {
val url = URL(urlStr)
val conn = if (httpPort == 0) {
url.openConnection()
@@ -389,7 +396,7 @@ object Utils {
}
fun setNightMode(context: Context) {
fun setNightMode() {
when (MmkvManager.decodeSettingsString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
@@ -409,7 +416,8 @@ object Utils {
}
fun getLocale(): Locale {
val langCode = MmkvManager.decodeSettingsString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
val langCode =
MmkvManager.decodeSettingsString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
val language = Language.fromCode(langCode)
return when (language) {
@@ -421,6 +429,7 @@ object Utils {
Language.RUSSIAN -> Locale("ru")
Language.PERSIAN -> Locale("fa")
Language.BANGLA -> Locale("bn")
Language.BAKHTIARI -> Locale("bqi", "IR")
}
}
@@ -454,7 +463,8 @@ object Utils {
return if (second) {
AppConfig.DelayTestUrl2
} else {
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL)
?: AppConfig.DelayTestUrl
}
}
@@ -482,5 +492,11 @@ object Utils {
return false
}
fun receiverFlags(): Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.RECEIVER_EXPORTED
} else {
ContextCompat.RECEIVER_NOT_EXPORTED
}
}

View File

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

View File

@@ -8,6 +8,7 @@ import android.content.IntentFilter
import android.content.res.AssetManager
import android.os.Build
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
@@ -25,6 +26,7 @@ import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -44,20 +46,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val updateTestResultAction by lazy { MutableLiveData<String>() }
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
/**
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
*/
fun startListenBroadcast() {
isRunning.value = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplication<AngApplication>().registerReceiver(
mMsgReceiver,
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY),
Context.RECEIVER_EXPORTED
)
} else {
getApplication<AngApplication>().registerReceiver(
mMsgReceiver,
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
)
}
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
ContextCompat.registerReceiver(getApplication(), mMsgReceiver, mFilter, Utils.receiverFlags())
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
}
@@ -260,10 +257,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val deleteServer = mutableListOf<String>()
serversCacheCopy.forEachIndexed { index, it ->
val outbound = it.second.getKeyProperty()
val outbound = it.second
serversCacheCopy.forEachIndexed { index2, it2 ->
if (index2 > index) {
val outbound2 = it2.second.getKeyProperty()
val outbound2 = it2.second
if (outbound.equals(outbound2) && !deleteServer.contains(it2.first)) {
deleteServer.add(it2.first)
}

View File

@@ -81,7 +81,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
// }
}
if (key == AppConfig.PREF_UI_MODE_NIGHT) {
Utils.setNightMode(getApplication())
Utils.setNightMode()
}
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More