Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6af25ae88 | |||
| 3ac89d68fb | |||
| 267a43fd97 | |||
| 6d0384b6f1 | |||
| 7ae4be402f | |||
| 46f0b7be5b | |||
| b253f2d947 | |||
| 444ade8afe | |||
| 522dbdd170 | |||
| d89238e1aa | |||
| 52710020ea | |||
| a18b8f4f2b | |||
| 01e0a6570d | |||
| cd6ef8f062 | |||
| ea383c43bd | |||
| 68e08e3866 | |||
| 329ed26e85 | |||
| c830a4cea4 | |||
| e7e088ec83 | |||
| cf6c814eb4 | |||
| 8f62e42ae8 | |||
| 89b4060ba2 | |||
| e4d4b329a0 | |||
| 09c5f41995 | |||
| 02b25787c1 | |||
| cdb050b6c5 | |||
| 21f47b536d | |||
| f14484c986 | |||
| 358dd78dc3 | |||
| d79e072316 | |||
| c24dee567a | |||
| 9f75bafcea | |||
| 2150452629 | |||
| 507a32a08e | |||
| 44dee8e850 | |||
| 5e4d9246c2 | |||
| 9fe7419467 | |||
| 41b2251dfe | |||
| 73fad43573 | |||
| 4676717582 | |||
| 834766e6e7 | |||
| e304dce347 | |||
| 61654aefeb | |||
| 1664aaa25b | |||
| 68f1f64f3d | |||
| 7bad57ca52 | |||
| 1c8e1f0993 | |||
| 6037ae6fc4 | |||
| dc1c5400b8 | |||
| 9ae4688171 | |||
| e3f39234b2 | |||
| 0df0b2d6ac | |||
| 9ce96d0591 | |||
| a7ef0618ba | |||
| cc9b083e5d | |||
| 5af322552c | |||
| fc852281dd | |||
| 0b2f036a22 | |||
| cf2becb5e9 | |||
| 82d8eba1b9 | |||
| 286ad34d94 | |||
| ff0bc6594d | |||
| 7b8113aef1 | |||
| 81d2ef5db5 | |||
| bdea3ef88c | |||
| c62c86fc29 | |||
| 59f698f755 | |||
| 9ac979006e | |||
| da6291a965 | |||
| bf6555e57c | |||
| 35b114220e | |||
| 800bb6a4e9 | |||
| 2c80521f5b | |||
| 6351ce5991 | |||
| 683362f0ee | |||
| 84fc909339 | |||
| 74171e26db | |||
| f25c0cc890 | |||
| 29848053a4 | |||
| 0d9856919e | |||
| 0ae7f2f7b3 | |||
| 3dde6b0ca3 | |||
| 6b28208044 | |||
| 13f855e3c4 | |||
| c870595e98 | |||
| ae19a3f68d | |||
| 545afc41b3 | |||
| 7177d88144 | |||
| 294ed50afd | |||
| 0105fe48f7 | |||
| c7ff23e3d5 | |||
| 23e9d7fde5 | |||
| c93edd8875 | |||
| bde37e38a7 | |||
| c401d63d2f | |||
| 52416dd43d | |||
| 59bd7128ae | |||
| 87f16467bb | |||
| bed0fd00bd | |||
| 0672af98f8 | |||
| 2341eceb65 | |||
| ec5f7245bf | |||
| caa2edcf05 | |||
| dbe78d0aa5 | |||
| 71f2f590a7 | |||
| ed26120581 | |||
| 71bd684b46 | |||
| 02ae19f0c7 | |||
| a46b0d58eb | |||
| d1262d169b | |||
| 4a85c95b02 | |||
| a368927b9f | |||
| 80feb69af5 | |||
| c09f0d5787 | |||
| ff667546b8 | |||
| 6a255cdfa4 | |||
| 2b5784df6f | |||
| a9665dd2d8 | |||
| 1e52877e93 | |||
| 062c0d8ddb | |||
| a7a70b448f | |||
| 3e52aeb804 | |||
| 4b4f5d145b | |||
| 775fa5ea62 | |||
| 34d8329c8a | |||
| 2e7ae732aa | |||
| 424287e258 | |||
| 40d03bbb96 | |||
| d768694445 | |||
| 56c7f2ef69 | |||
| 3839b0c59c | |||
| 77cab14ae8 | |||
| b0dbd4c7ca | |||
| 50ca2e0e69 | |||
| 0bf8beda94 | |||
| 4d334929c9 | |||
| ccfdf096f9 | |||
| b4f2af2778 | |||
| 32b9e4855c | |||
| bdbce5147e | |||
| a4833506ae | |||
| d4e8072248 | |||
| 207ca93735 | |||
| a5bb39ac8a | |||
| 073c7c0410 | |||
| 7e88e3ba4f |
@@ -0,0 +1,64 @@
|
||||
name: Build APK
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
XRAY_CORE_VERSION:
|
||||
description: 'Xray core version or commit hash'
|
||||
required: false
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21.4'
|
||||
|
||||
- name: Install gomobile
|
||||
run: |
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
|
||||
|
||||
- name: Setup Android environment
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
|
||||
- name: Build dependencies
|
||||
run: |
|
||||
mkdir ${{ github.workspace }}/build
|
||||
cd ${{ github.workspace }}/build
|
||||
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
|
||||
cd AndroidLibXrayLite
|
||||
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
|
||||
gomobile init
|
||||
go mod tidy -v
|
||||
gomobile bind -v -androidapi 19 -ldflags='-s -w' ./
|
||||
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
||||
|
||||
- name: Build APK
|
||||
run: |
|
||||
cd ${{ github.workspace }}/V2rayNG
|
||||
chmod 777 *
|
||||
sed -i 's/org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8/org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8/' ${{ github.workspace }}/V2rayNG/gradle.properties
|
||||
./gradlew assembleDebug
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: apk
|
||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/
|
||||
@@ -1,29 +1,40 @@
|
||||
v2rayNG 隐私条款
|
||||
**v2rayNG 隐私权政策**
|
||||
|
||||
最后更新 2017-11-22
|
||||
本政策自2023年11月17日起施行
|
||||
|
||||
v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。**
|
||||
2dust 将 v2rayNG 应用程序构建为开源应用程序。 本服务由 2dust 免费提供,并且旨在按原样使用。
|
||||
|
||||
1. 信息收集
|
||||
v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私权政策。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。**
|
||||
|
||||
v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。
|
||||
**信息收集**
|
||||
|
||||
v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。
|
||||
v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私权政策。
|
||||
|
||||
当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。**
|
||||
v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。
|
||||
|
||||
2. 信息共享
|
||||
当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。**
|
||||
|
||||
我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。
|
||||
**信息共享**
|
||||
|
||||
3. 信息存留
|
||||
我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。
|
||||
|
||||
除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。
|
||||
**信息存留**
|
||||
|
||||
4. 信息泄露
|
||||
除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。
|
||||
|
||||
我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。**
|
||||
**信息泄露**
|
||||
|
||||
5. 条款修改
|
||||
我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。**
|
||||
|
||||
**儿童隐私**
|
||||
|
||||
这些服务不针对 13 岁以下的任何人。我不会故意收集 13 岁以下儿童的个人身份信息。 如果我发现 13 岁以下的儿童向我提供了个人信息,我会立即从我们的服务器中删除该信息。 如果您是父母或监护人,并且您知道您的孩子向我们提供了个人信息,请与我联系,以便我能够采取必要的行动。
|
||||
|
||||
**条款修改**
|
||||
|
||||
我们保留修改这份隐私权政策的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私权政策。**
|
||||
|
||||
**联系我们**
|
||||
|
||||
如果您对我的隐私政策有任何疑问或建议,请随时通过 CaptainIronng@protonmail.com 与我联系。
|
||||
|
||||
我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。**
|
||||
|
||||
@@ -8,8 +8,8 @@ android {
|
||||
buildToolsVersion "$buildToolsVer"
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility = "8"
|
||||
sourceCompatibility = "8"
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
@@ -17,8 +17,8 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
||||
multiDexEnabled true
|
||||
versionCode 518
|
||||
versionName "1.8.6"
|
||||
versionCode 540
|
||||
versionName "1.8.15"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -101,13 +101,13 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.7'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
|
||||
|
||||
// Androidx ktx
|
||||
implementation 'androidx.activity:activity-ktx:1.7.1'
|
||||
@@ -120,16 +120,21 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
|
||||
implementation 'com.tencent:mmkv-static:1.2.15'
|
||||
implementation 'com.tencent:mmkv-static:1.3.3'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'io.reactivex:rxjava:1.3.8'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||
implementation 'com.blacksquircle.ui:editorkit:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-base:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-json:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:editorkit:2.8.0'
|
||||
implementation 'com.blacksquircle.ui:language-base:2.8.0'
|
||||
implementation 'com.blacksquircle.ui:language-json:2.8.0'
|
||||
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
|
||||
implementation 'com.google.zxing:core:3.5.1'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
|
||||
def work_version = "2.8.1"
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
implementation "androidx.work:work-multiprocess:$work_version"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="MissingLeanbackLauncher">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
@@ -9,6 +10,8 @@
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true"/>
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
@@ -24,6 +27,9 @@
|
||||
<!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||
android:minSdkVersion="34" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
@@ -48,7 +54,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
<!-- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>-->
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
@@ -125,6 +131,7 @@
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
@@ -132,12 +139,19 @@
|
||||
<meta-data
|
||||
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
|
||||
android:value="true" />
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="vpn" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.V2RayProxyOnlyService"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="proxy" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.V2RayTestService"
|
||||
@@ -164,11 +178,15 @@
|
||||
android:name=".service.QSTileService"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:label="@string/app_tile_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="tile" />
|
||||
</service>
|
||||
<!-- =====================Tasker===================== -->
|
||||
<activity
|
||||
@@ -190,6 +208,18 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<!-- =====================Tasker===================== -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Configuration
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
class AngApplication : MultiDexApplication() {
|
||||
class AngApplication : MultiDexApplication(), Configuration.Provider {
|
||||
companion object {
|
||||
const val PREF_LAST_VERSION = "pref_last_version"
|
||||
lateinit var application: AngApplication
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
application = this
|
||||
}
|
||||
|
||||
var firstRun = false
|
||||
@@ -25,4 +33,10 @@ class AngApplication : MultiDexApplication() {
|
||||
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
||||
MMKV.initialize(this)
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration {
|
||||
return Configuration.Builder()
|
||||
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ object AppConfig {
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
||||
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
|
||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||
const val PREF_MUX_CONCURRENCY = "pref_mux_concurency"
|
||||
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency"
|
||||
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
|
||||
|
||||
const val HTTP_PROTOCOL: String = "http://"
|
||||
const val HTTPS_PROTOCOL: String = "https://"
|
||||
@@ -58,12 +62,15 @@ object AppConfig {
|
||||
const val TAG_DIRECT = "direct"
|
||||
const val TAG_BLOCKED = "block"
|
||||
|
||||
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||
const val androidpackagenamelistUrl =
|
||||
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||
const val v2rayCustomRoutingListUrl =
|
||||
"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
||||
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
|
||||
const val v2rayNGPrivacyPolicy = "https://raw.githubusercontent.com/2dust/v2rayNG/master/CR.md"
|
||||
const val promotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
|
||||
const val geoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/";
|
||||
const val geoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
|
||||
|
||||
const val DNS_AGENT = "1.1.1.1"
|
||||
const val DNS_DIRECT = "223.5.5.5"
|
||||
@@ -71,6 +78,9 @@ object AppConfig {
|
||||
const val PORT_LOCAL_DNS = "10853"
|
||||
const val PORT_SOCKS = "10808"
|
||||
const val PORT_HTTP = "10809"
|
||||
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
|
||||
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
||||
const val WIREGUARD_LOCAL_MTU = "1420"
|
||||
|
||||
const val MSG_REGISTER_CLIENT = 1
|
||||
const val MSG_STATE_RUNNING = 11
|
||||
@@ -87,4 +97,13 @@ object AppConfig {
|
||||
const val MSG_MEASURE_CONFIG = 7
|
||||
const val MSG_MEASURE_CONFIG_SUCCESS = 71
|
||||
const val MSG_MEASURE_CONFIG_CANCEL = 72
|
||||
|
||||
// subscription settings
|
||||
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
|
||||
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
|
||||
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
|
||||
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
|
||||
|
||||
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
|
||||
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
|
||||
}
|
||||
|
||||
@@ -19,23 +19,32 @@ data class ServerConfig(
|
||||
when(configType) {
|
||||
EConfigType.VMESS, EConfigType.VLESS ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
EConfigType.CUSTOM, EConfigType.WIREGUARD ->
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
EConfigType.CUSTOM ->
|
||||
return ServerConfig(configType = configType)
|
||||
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
EConfigType.WIREGUARD ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
secretKey = "",
|
||||
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,10 +69,6 @@ data class ServerConfig(
|
||||
fun getV2rayPointDomainAndPort(): String {
|
||||
val address = getProxyOutbound()?.getServerAddress().orEmpty()
|
||||
val port = getProxyOutbound()?.getServerPort()
|
||||
return if (Utils.isIpv6Address(address)) {
|
||||
String.format("[%s]:%s", address, port)
|
||||
} else {
|
||||
String.format("%s:%s", address, port)
|
||||
}
|
||||
return Utils.getIpv6Address(address) + ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class SubscriptionItem(
|
||||
var remarks: String = "",
|
||||
var url: String = "",
|
||||
var enabled: Boolean = true,
|
||||
val addedTime: Long = System.currentTimeMillis()) {
|
||||
}
|
||||
var remarks: String = "",
|
||||
var url: String = "",
|
||||
var enabled: Boolean = true,
|
||||
val addedTime: Long = System.currentTimeMillis(),
|
||||
var lastUpdated: Long = -1,
|
||||
var autoUpdate: Boolean = false,
|
||||
val updateInterval: Int? = null,
|
||||
)
|
||||
@@ -74,7 +74,7 @@ data class V2rayConfig(
|
||||
var response: Response? = null,
|
||||
/*DNS*/
|
||||
val network: String? = null,
|
||||
val address: Any? = null,
|
||||
var address: Any? = null,
|
||||
val port: Int? = null,
|
||||
/*Freedom*/
|
||||
var domainStrategy: String? = null,
|
||||
@@ -83,8 +83,10 @@ data class V2rayConfig(
|
||||
/*Loopback*/
|
||||
val inboundTag: String? = null,
|
||||
/*Wireguard*/
|
||||
val secretKey: String? = null,
|
||||
var secretKey: String? = null,
|
||||
val peers: List<WireGuardBean>? = null,
|
||||
var reserved: List<Int>? = null,
|
||||
var mtu :Int? = null
|
||||
) {
|
||||
|
||||
data class VnextBean(var address: String = "",
|
||||
@@ -292,7 +294,10 @@ data class V2rayConfig(
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
|
||||
data class MuxBean(var enabled: Boolean,
|
||||
var concurrency: Int = 8,
|
||||
var xudpConcurrency: Int = 8,
|
||||
var xudpProxyUDP443: String = "",)
|
||||
|
||||
fun getServerAddress(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|
||||
@@ -17,16 +17,16 @@ val Context.v2RayApplication: AngApplication
|
||||
get() = applicationContext as AngApplication
|
||||
|
||||
fun Context.toast(message: Int): Toast = ToastCompat
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
fun Context.toast(message: CharSequence): Toast = ToastCompat
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
|
||||
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
|
||||
@@ -77,4 +77,8 @@ val URLConnection.responseLength: Long
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) contentLengthLong else contentLength.toLong()
|
||||
|
||||
val URI.idnHost: String
|
||||
get() = (host!!).replace("[", "").replace("]", "")
|
||||
get() = (host!!).replace("[", "").replace("]", "")
|
||||
|
||||
fun String.removeWhiteSpace(): String {
|
||||
return this.replace(" ", "")
|
||||
}
|
||||
@@ -38,12 +38,18 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
})
|
||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||
if (isRunning) {
|
||||
if (!Utils.getDarkModeStatus(context)) {
|
||||
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name)
|
||||
}
|
||||
remoteViews.setInt(
|
||||
R.id.layout_switch,
|
||||
"setBackgroundResource",
|
||||
R.drawable.ic_rounded_corner_theme
|
||||
R.drawable.ic_rounded_corner_active
|
||||
)
|
||||
} else {
|
||||
if (!Utils.getDarkModeStatus(context)) {
|
||||
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name_black)
|
||||
}
|
||||
remoteViews.setInt(
|
||||
R.id.layout_switch,
|
||||
"setBackgroundResource",
|
||||
|
||||
@@ -36,7 +36,12 @@ class QSTileService : TileService() {
|
||||
super.onStartListening()
|
||||
setState(Tile.STATE_INACTIVE)
|
||||
mMsgReceive = ReceiveMessageHandler(this)
|
||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
||||
}
|
||||
|
||||
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
object SubscriptionUpdater {
|
||||
|
||||
const val notificationChannel = "subscription_update_channel"
|
||||
|
||||
class UpdateTask(context: Context, params: WorkerParameters) :
|
||||
CoroutineWorker(context, params) {
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||
private val notification =
|
||||
NotificationCompat.Builder(applicationContext, notificationChannel)
|
||||
.setWhen(0)
|
||||
.setTicker("Update")
|
||||
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
|
||||
.setSmallIcon(R.drawable.ic_stat_name)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun doWork(): Result {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "subscription automatic update starting")
|
||||
|
||||
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
|
||||
|
||||
for (i in subs) {
|
||||
val subscription = i.second
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notification.setChannelId(notificationChannel)
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
notificationChannel,
|
||||
"Subscription Update Service",
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
notificationManager.notify(3, notification.build())
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"subscription automatic update: ---${subscription.remarks}"
|
||||
)
|
||||
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
||||
importBatchConfig(configs, i.first)
|
||||
notification.setContentText("Updating ${subscription.remarks}")
|
||||
}
|
||||
notificationManager.cancel(3)
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
fun importBatchConfig(server: String?, subid: String = "") {
|
||||
val append = subid.isEmpty()
|
||||
|
||||
val count = AngConfigManager.importBatchConfig(server, subid, append)
|
||||
if (count <= 0) {
|
||||
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
|
||||
}
|
||||
if (count <= 0) {
|
||||
AngConfigManager.appendCustomConfigServer(server, subid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ object V2RayServiceManager {
|
||||
set(value) {
|
||||
field = value
|
||||
Seq.setContext(value?.get()?.getService()?.applicationContext)
|
||||
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()))
|
||||
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
|
||||
}
|
||||
var currentConfig: ServerConfig? = null
|
||||
|
||||
@@ -133,7 +133,11 @@ object V2RayServiceManager {
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
service.registerReceiver(mMsgReceive, mFilter)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
service.registerReceiver(mMsgReceive, mFilter)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class V2RayTestService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Seq.setContext(this)
|
||||
Libv2ray.initV2Env(Utils.userAssetPath(this))
|
||||
Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
|
||||
@@ -2,13 +2,25 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.v2ray.ang.util.MyContextWrapper
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
if (!Utils.getDarkModeStatus(this)) {
|
||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
|
||||
@@ -25,13 +25,12 @@ class LogcatActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
title = getString(R.string.title_logcat)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
logcat(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
binding.navView.setNavigationItemSelectedListener(this)
|
||||
binding.version.text = "v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})"
|
||||
"v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})".also { binding.version.text = it }
|
||||
|
||||
setupViewModel()
|
||||
copyAssets()
|
||||
@@ -131,11 +131,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||
adapter.isRunning = isRunning
|
||||
if (isRunning) {
|
||||
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorSelected))
|
||||
if (!Utils.getDarkModeStatus(this)) {
|
||||
binding.fab.setImageResource(R.drawable.ic_stat_name)
|
||||
}
|
||||
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_orange))
|
||||
setTestState(getString(R.string.connection_connected))
|
||||
binding.layoutTest.isFocusable = true
|
||||
} else {
|
||||
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorUnselected))
|
||||
if (!Utils.getDarkModeStatus(this)) {
|
||||
binding.fab.setImageResource(R.drawable.ic_stat_name)
|
||||
}
|
||||
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_grey))
|
||||
setTestState(getString(R.string.connection_not_connected))
|
||||
binding.layoutTest.isFocusable = false
|
||||
}
|
||||
@@ -247,6 +253,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
importManually(EConfigType.TROJAN.value)
|
||||
true
|
||||
}
|
||||
R.id.import_manually_wireguard -> {
|
||||
importManually(EConfigType.WIREGUARD.value)
|
||||
true
|
||||
}
|
||||
R.id.import_config_custom_clipboard -> {
|
||||
importConfigCustomClipboard()
|
||||
true
|
||||
@@ -409,6 +419,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
if (count <= 0) {
|
||||
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
|
||||
}
|
||||
if (count <= 0) {
|
||||
count = AngConfigManager.appendCustomConfigServer(server, subid2)
|
||||
}
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
mainViewModel.reloadServerList()
|
||||
@@ -676,6 +689,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
R.id.logcat -> {
|
||||
startActivity(Intent(this, LogcatActivity::class.java))
|
||||
}
|
||||
R.id.privacy_policy-> {
|
||||
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy)
|
||||
}
|
||||
}
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
return true
|
||||
|
||||
@@ -96,7 +96,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
||||
}
|
||||
}
|
||||
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
|
||||
val strState = "${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
|
||||
holder.itemMainBinding.tvStatistics.text = strState
|
||||
|
||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
@@ -41,8 +40,6 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||
binding.recyclerView.addItemDecoration(dividerItemDecoration)
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ class RoutingSettingsActivity : BaseActivity() {
|
||||
setContentView(view)
|
||||
|
||||
title = getString(R.string.title_pref_routing_custom)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val fragments = ArrayList<Fragment>()
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity.RESULT_OK
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
@@ -18,7 +18,6 @@ import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoutingSettingsFragment : Fragment() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
@@ -31,16 +31,6 @@ class ScannerActivity : BaseActivity(){
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
|
||||
launchScan()
|
||||
}
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private fun launchScan(){
|
||||
@@ -64,7 +54,7 @@ class ScannerActivity : BaseActivity(){
|
||||
private fun finished(text: String) {
|
||||
val intent = Intent()
|
||||
intent.putExtra("SCAN_RESULT", text)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
@@ -10,22 +10,32 @@ import androidx.appcompat.app.AlertDialog
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
||||
import com.v2ray.ang.extension.removeWhiteSpace
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.Utils.getIpv6Address
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SETTING,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||
private val isRunning by lazy {
|
||||
intent.getBooleanExtra("isRunning", false)
|
||||
@@ -33,7 +43,8 @@ class ServerActivity : BaseActivity() {
|
||||
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
|
||||
}
|
||||
private val createConfigType by lazy {
|
||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value))
|
||||
?: EConfigType.VMESS
|
||||
}
|
||||
private val subscriptionId by lazy {
|
||||
intent.getStringExtra("subscriptionId")
|
||||
@@ -66,12 +77,13 @@ class ServerActivity : BaseActivity() {
|
||||
private val allowinsecures: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.allowinsecures)
|
||||
}
|
||||
private val uTlsItems: Array<out String> by lazy {
|
||||
private val uTlsItems: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.streamsecurity_utls)
|
||||
}
|
||||
private val alpns: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.streamsecurity_alpn)
|
||||
}
|
||||
|
||||
// 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
|
||||
// protocols. Use findViewById manually ensures the xml are de-coupled with the activity logic.
|
||||
@@ -103,26 +115,38 @@ class ServerActivity : BaseActivity() {
|
||||
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) }
|
||||
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
|
||||
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) }
|
||||
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
|
||||
private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) }
|
||||
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
|
||||
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
|
||||
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||
when(config?.configType ?: createConfigType) {
|
||||
when (config?.configType ?: createConfigType) {
|
||||
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
|
||||
EConfigType.CUSTOM -> return
|
||||
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||
else -> setContentView(R.layout.activity_server_vmess)
|
||||
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
|
||||
}
|
||||
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
override fun onItemSelected(
|
||||
parent: AdapterView<*>?,
|
||||
view: View?,
|
||||
position: Int,
|
||||
id: Long
|
||||
) {
|
||||
val types = transportTypes(networks[position])
|
||||
sp_header_type?.isEnabled = types.size > 1
|
||||
val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
|
||||
val adapter =
|
||||
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")
|
||||
@@ -134,12 +158,18 @@ class ServerActivity : BaseActivity() {
|
||||
et_path?.text = Utils.getEditable(transportDetails[2])
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
sp_stream_security?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
override fun onItemSelected(
|
||||
parent: AdapterView<*>?,
|
||||
view: View?,
|
||||
position: Int,
|
||||
id: Long
|
||||
) {
|
||||
if (streamSecuritys[position].isBlank()) {
|
||||
container_sni?.visibility = View.GONE
|
||||
container_fingerprint?.visibility = View.GONE
|
||||
@@ -166,6 +196,7 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected(p0: AdapterView<*>?) {
|
||||
// do nothing
|
||||
}
|
||||
@@ -175,7 +206,6 @@ class ServerActivity : BaseActivity() {
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,37 +213,67 @@ class ServerActivity : BaseActivity() {
|
||||
*/
|
||||
private fun bindingServer(config: ServerConfig): Boolean {
|
||||
val outbound = config.getProxyOutbound() ?: return false
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||
|
||||
et_remarks.text = Utils.getEditable(config.remarks)
|
||||
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
|
||||
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
|
||||
et_port.text =
|
||||
Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
|
||||
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
||||
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
||||
et_alterId?.text =
|
||||
Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
||||
if (config.configType == EConfigType.SOCKS) {
|
||||
et_security?.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||
et_security?.text =
|
||||
Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
|
||||
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
|
||||
val flow = Utils.arrayFind(
|
||||
flows,
|
||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty()
|
||||
)
|
||||
if (flow >= 0) {
|
||||
sp_flow?.setSelection(flow)
|
||||
}
|
||||
} else if (config.configType == EConfigType.TROJAN) {
|
||||
val flow = Utils.arrayFind(flows, outbound.settings?.servers?.get(0)?.flow.orEmpty())
|
||||
if (flow >= 0) {
|
||||
sp_flow?.setSelection(flow)
|
||||
} else if (config.configType == EConfigType.WIREGUARD) {
|
||||
et_public_key?.text =
|
||||
Utils.getEditable(outbound.settings?.peers?.get(0)?.publicKey.orEmpty())
|
||||
if (outbound.settings?.reserved == null) {
|
||||
et_reserved1?.text = Utils.getEditable("0")
|
||||
et_reserved2?.text = Utils.getEditable("0")
|
||||
et_reserved3?.text = Utils.getEditable("0")
|
||||
} else {
|
||||
et_reserved1?.text =
|
||||
Utils.getEditable(outbound.settings?.reserved?.get(0).toString())
|
||||
et_reserved2?.text =
|
||||
Utils.getEditable(outbound.settings?.reserved?.get(1).toString())
|
||||
et_reserved3?.text =
|
||||
Utils.getEditable(outbound.settings?.reserved?.get(2).toString())
|
||||
}
|
||||
if (outbound.settings?.address == null) {
|
||||
et_local_address?.text =
|
||||
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
|
||||
} else {
|
||||
val list = outbound.settings?.address as List<*>
|
||||
et_local_address?.text = Utils.getEditable(list.joinToString())
|
||||
}
|
||||
if (outbound.settings?.mtu == null) {
|
||||
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
||||
} else {
|
||||
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
|
||||
}
|
||||
}
|
||||
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
|
||||
val securityEncryptions =
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||
val security =
|
||||
Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
|
||||
if (security >= 0) {
|
||||
sp_security?.setSelection(security)
|
||||
}
|
||||
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return true
|
||||
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
|
||||
if (streamSecurity >= 0) {
|
||||
sp_stream_security?.setSelection(streamSecurity)
|
||||
(streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||
(streamSetting.tlsSettings ?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||
container_sni?.visibility = View.VISIBLE
|
||||
container_fingerprint?.visibility = View.VISIBLE
|
||||
container_alpn?.visibility = View.VISIBLE
|
||||
@@ -223,12 +283,16 @@ class ServerActivity : BaseActivity() {
|
||||
sp_stream_fingerprint?.setSelection(utlsIndex)
|
||||
}
|
||||
tlsSetting.alpn?.let {
|
||||
val alpnIndex = Utils.arrayFind(alpns, Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!)
|
||||
val alpnIndex = Utils.arrayFind(
|
||||
alpns,
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!
|
||||
)
|
||||
sp_stream_alpn?.setSelection(alpnIndex)
|
||||
}
|
||||
if (streamSetting.tlsSettings != null) {
|
||||
container_allow_insecure?.visibility = View.VISIBLE
|
||||
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||
val allowinsecure =
|
||||
Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||
if (allowinsecure >= 0) {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
@@ -283,6 +347,13 @@ class ServerActivity : BaseActivity() {
|
||||
|
||||
//et_security.text = null
|
||||
sp_flow?.setSelection(0)
|
||||
et_public_key?.text = null
|
||||
et_reserved1?.text = Utils.getEditable("0")
|
||||
et_reserved2?.text = Utils.getEditable("0")
|
||||
et_reserved3?.text = Utils.getEditable("0")
|
||||
et_local_address?.text =
|
||||
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
|
||||
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -303,9 +374,14 @@ class ServerActivity : BaseActivity() {
|
||||
toast(R.string.server_lab_port)
|
||||
return false
|
||||
}
|
||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
|
||||
val config =
|
||||
MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
|
||||
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
|
||||
toast(R.string.server_lab_id)
|
||||
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
|
||||
toast(R.string.server_lab_id3)
|
||||
} else {
|
||||
toast(R.string.server_lab_id)
|
||||
}
|
||||
return false
|
||||
}
|
||||
sp_stream_security?.let {
|
||||
@@ -329,10 +405,14 @@ class ServerActivity : BaseActivity() {
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
saveServers(server, port, config)
|
||||
}
|
||||
val wireguard = config.outboundBean?.settings
|
||||
wireguard?.peers?.get(0)?.let { _ ->
|
||||
savePeer(wireguard, port)
|
||||
}
|
||||
config.outboundBean?.streamSettings?.let {
|
||||
saveStreamSettings(it)
|
||||
}
|
||||
if(config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
config.subscriptionId = subscriptionId!!
|
||||
}
|
||||
|
||||
@@ -342,7 +422,11 @@ class ServerActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) {
|
||||
private fun saveVnext(
|
||||
vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean,
|
||||
port: Int,
|
||||
config: ServerConfig
|
||||
) {
|
||||
vnext.address = et_address.text.toString().trim()
|
||||
vnext.port = port
|
||||
vnext.users[0].id = et_id.text.toString().trim()
|
||||
@@ -355,7 +439,11 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) {
|
||||
private fun saveServers(
|
||||
server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean,
|
||||
port: Int,
|
||||
config: ServerConfig
|
||||
) {
|
||||
server.address = et_address.text.toString().trim()
|
||||
server.port = port
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||
@@ -365,7 +453,8 @@ class ServerActivity : BaseActivity() {
|
||||
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
|
||||
server.users = null
|
||||
} else {
|
||||
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
val socksUsersBean =
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
socksUsersBean.user = et_security?.text.toString().trim()
|
||||
socksUsersBean.pass = et_id.text.toString().trim()
|
||||
server.users = listOf(socksUsersBean)
|
||||
@@ -375,6 +464,23 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePeer(wireguard: V2rayConfig.OutboundBean.OutSettingsBean, port: Int) {
|
||||
wireguard.secretKey = et_id.text.toString().trim()
|
||||
wireguard.peers?.get(0)?.publicKey = et_public_key?.text.toString().trim()
|
||||
wireguard.peers?.get(0)?.endpoint =
|
||||
getIpv6Address(et_address.text.toString().trim()) + ":" + port
|
||||
val reserved1 = Utils.parseInt(et_reserved1?.text.toString())
|
||||
val reserved2 = Utils.parseInt(et_reserved2?.text.toString())
|
||||
val reserved3 = Utils.parseInt(et_reserved3?.text.toString())
|
||||
if (reserved1 > 0 || reserved2 > 0 || reserved3 > 0) {
|
||||
wireguard.reserved = listOf(reserved1, reserved2, reserved3)
|
||||
} else {
|
||||
wireguard.reserved = null
|
||||
}
|
||||
wireguard.address = et_local_address?.text.toString().removeWhiteSpace().split(",")
|
||||
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
|
||||
}
|
||||
|
||||
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
|
||||
val network = sp_network?.selectedItemPosition ?: return
|
||||
val type = sp_header_type?.selectedItemPosition ?: return
|
||||
@@ -390,15 +496,15 @@ class ServerActivity : BaseActivity() {
|
||||
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
|
||||
|
||||
var sni = streamSetting.populateTransportSettings(
|
||||
transport = networks[network],
|
||||
headerType = transportTypes(networks[network])[type],
|
||||
host = requestHost,
|
||||
path = path,
|
||||
seed = path,
|
||||
quicSecurity = requestHost,
|
||||
key = path,
|
||||
mode = transportTypes(networks[network])[type],
|
||||
serviceName = path
|
||||
transport = networks[network],
|
||||
headerType = transportTypes(networks[network])[type],
|
||||
host = requestHost,
|
||||
path = path,
|
||||
seed = path,
|
||||
quicSecurity = requestHost,
|
||||
key = path,
|
||||
mode = transportTypes(networks[network])[type],
|
||||
serviceName = path
|
||||
)
|
||||
if (sniField.isNotBlank()) {
|
||||
sni = sniField
|
||||
@@ -410,14 +516,14 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
streamSetting.populateTlsSettings(
|
||||
streamSecurity = streamSecuritys[streamSecurity],
|
||||
allowInsecure = allowInsecure,
|
||||
sni = sni,
|
||||
fingerprint = uTlsItems[utlsIndex],
|
||||
alpns = alpns[alpnIndex],
|
||||
publicKey = publicKey,
|
||||
shortId = shortId,
|
||||
spiderX = spiderX
|
||||
streamSecurity = streamSecuritys[streamSecurity],
|
||||
allowInsecure = allowInsecure,
|
||||
sni = sni,
|
||||
fingerprint = uTlsItems[utlsIndex],
|
||||
alpns = alpns[alpnIndex],
|
||||
publicKey = publicKey,
|
||||
shortId = shortId,
|
||||
spiderX = spiderX
|
||||
)
|
||||
}
|
||||
|
||||
@@ -426,12 +532,15 @@ class ServerActivity : BaseActivity() {
|
||||
"tcp" -> {
|
||||
tcpTypes
|
||||
}
|
||||
|
||||
"kcp", "quic" -> {
|
||||
kcpAndQuicTypes
|
||||
}
|
||||
|
||||
"grpc" -> {
|
||||
grpcModes
|
||||
}
|
||||
|
||||
else -> {
|
||||
arrayOf("---")
|
||||
}
|
||||
@@ -482,10 +591,12 @@ class ServerActivity : BaseActivity() {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.google.gson.*
|
||||
import com.tencent.mmkv.MMKV
|
||||
@@ -38,6 +39,9 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (!Utils.getDarkModeStatus(this)) {
|
||||
binding.editor.colorScheme = EditorTheme.INTELLIJ_LIGHT
|
||||
}
|
||||
binding.editor.language = JsonLanguage()
|
||||
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||
if (config != null) {
|
||||
@@ -45,7 +49,6 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,10 +6,16 @@ import android.text.TextUtils
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.preference.*
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.multiprocess.RemoteWorkManager
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.service.SubscriptionUpdater
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
@@ -20,8 +26,6 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
title = getString(R.string.title_settings)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
settingsViewModel.startListenPreferenceChange()
|
||||
}
|
||||
|
||||
@@ -31,12 +35,21 @@ class SettingsActivity : BaseActivity() {
|
||||
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
||||
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
||||
|
||||
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
||||
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
||||
private val muxXudpConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_XUDP_CONCURRENCY) }
|
||||
private val muxXudpQuic by lazy { findPreference<ListPreference>(AppConfig.PREF_MUX_XUDP_QUIC) }
|
||||
|
||||
|
||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
||||
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
||||
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
|
||||
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
||||
private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
|
||||
private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
|
||||
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
|
||||
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
|
||||
@@ -51,6 +64,27 @@ class SettingsActivity : BaseActivity() {
|
||||
false
|
||||
}
|
||||
|
||||
autoUpdateCheck?.setOnPreferenceChangeListener { _, newValue ->
|
||||
val value = newValue as Boolean
|
||||
autoUpdateCheck?.isChecked = value
|
||||
autoUpdateInterval?.isEnabled = value
|
||||
autoUpdateInterval?.text?.toLong()?.let {
|
||||
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
autoUpdateInterval?.setOnPreferenceChangeListener { _, any ->
|
||||
var nval = any as String
|
||||
|
||||
autoUpdateInterval?.summary = nval
|
||||
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
|
||||
nval =
|
||||
if (TextUtils.isEmpty(nval) or (nval.toLong() < 15)) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
|
||||
configureUpdateTask(nval.toLong())
|
||||
true
|
||||
}
|
||||
|
||||
// licenses.onClick {
|
||||
// val fragment = LicensesDialogFragment.Builder(act)
|
||||
// .setNotices(R.raw.licenses)
|
||||
@@ -92,13 +126,14 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
localDns?.setOnPreferenceChangeListener{ _, any ->
|
||||
localDns?.setOnPreferenceChangeListener { _, any ->
|
||||
updateLocalDns(any as Boolean)
|
||||
true
|
||||
}
|
||||
localDnsPort?.setOnPreferenceChangeListener { _, any ->
|
||||
val nval = any as String
|
||||
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval
|
||||
localDnsPort?.summary =
|
||||
if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval
|
||||
true
|
||||
}
|
||||
vpnDns?.setOnPreferenceChangeListener { _, any ->
|
||||
@@ -121,18 +156,36 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
mode?.dialogLayoutResource = R.layout.preference_with_help_link
|
||||
//loglevel.summary = "LogLevel"
|
||||
mux?.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateMux(newValue as Boolean)
|
||||
true
|
||||
}
|
||||
muxConcurrency?.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateMuxConcurrency(newValue as String)
|
||||
true
|
||||
}
|
||||
muxXudpConcurrency?.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateMuxXudpConcurrency(newValue as String)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val defaultSharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
|
||||
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
||||
|
||||
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
||||
localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
|
||||
socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
||||
httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
||||
updateMux(defaultSharedPreferences.getBoolean(AppConfig.PREF_MUX_ENABLED, false))
|
||||
muxConcurrency?.summary = defaultSharedPreferences.getString(AppConfig.PREF_MUX_CONCURRENCY, "8")
|
||||
muxXudpConcurrency?.summary = defaultSharedPreferences.getString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
|
||||
autoUpdateInterval?.summary = defaultSharedPreferences.getString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
|
||||
autoUpdateInterval?.isEnabled = defaultSharedPreferences.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||
|
||||
if (TextUtils.isEmpty(remoteDnsString)) {
|
||||
remoteDnsString = AppConfig.DNS_AGENT
|
||||
@@ -141,7 +194,8 @@ class SettingsActivity : BaseActivity() {
|
||||
domesticDns?.summary = AppConfig.DNS_DIRECT
|
||||
}
|
||||
remoteDns?.summary = remoteDnsString
|
||||
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
||||
vpnDns?.summary =
|
||||
defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
||||
|
||||
if (TextUtils.isEmpty(localDnsPort?.summary)) {
|
||||
localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS
|
||||
@@ -155,17 +209,24 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun updateMode(mode: String?) {
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val defaultSharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val vpn = mode == "VPN"
|
||||
perAppProxy?.isEnabled = vpn
|
||||
perAppProxy?.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
perAppProxy?.isChecked =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
localDns?.isEnabled = vpn
|
||||
fakeDns?.isEnabled = vpn
|
||||
localDnsPort?.isEnabled = vpn
|
||||
vpnDns?.isEnabled = vpn
|
||||
if (vpn) {
|
||||
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
|
||||
updateLocalDns(
|
||||
defaultSharedPreferences.getBoolean(
|
||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +235,58 @@ class SettingsActivity : BaseActivity() {
|
||||
localDnsPort?.isEnabled = enabled
|
||||
vpnDns?.isEnabled = !enabled
|
||||
}
|
||||
|
||||
private fun configureUpdateTask(interval: Long) {
|
||||
val rw = RemoteWorkManager.getInstance(AngApplication.application)
|
||||
rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
|
||||
rw.enqueueUniquePeriodicWork(
|
||||
AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
PeriodicWorkRequest.Builder(
|
||||
SubscriptionUpdater.UpdateTask::class.java,
|
||||
interval,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.apply {
|
||||
setInitialDelay(interval, TimeUnit.MINUTES)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun cancelUpdateTask() {
|
||||
val rw = RemoteWorkManager.getInstance(AngApplication.application)
|
||||
rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
|
||||
}
|
||||
|
||||
private fun updateMux(enabled: Boolean) {
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
muxConcurrency?.isEnabled = enabled
|
||||
muxXudpConcurrency?.isEnabled = enabled
|
||||
muxXudpQuic?.isEnabled = enabled
|
||||
if (enabled) {
|
||||
updateMuxConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_CONCURRENCY, "8"))
|
||||
updateMuxXudpConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMuxConcurrency(value: String?) {
|
||||
if (value == null) {
|
||||
} else {
|
||||
val concurrency = value.toIntOrNull() ?: 8
|
||||
muxConcurrency?.summary = concurrency.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMuxXudpConcurrency(value: String?) {
|
||||
if (value == null) {
|
||||
muxXudpQuic?.isEnabled = true
|
||||
} else {
|
||||
val concurrency = value.toIntOrNull() ?: 8
|
||||
muxXudpConcurrency?.summary = concurrency.toString()
|
||||
muxXudpQuic?.isEnabled = concurrency >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onModeHelpClicked(view: View) {
|
||||
|
||||
@@ -5,14 +5,22 @@ import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.multiprocess.RemoteWorkManager
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.SubscriptionUpdater
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SubEditActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubEditBinding
|
||||
@@ -36,7 +44,6 @@ class SubEditActivity : BaseActivity() {
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,6 +53,7 @@ class SubEditActivity : BaseActivity() {
|
||||
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
|
||||
binding.etUrl.text = Utils.getEditable(subItem.url)
|
||||
binding.chkEnable.isChecked = subItem.enabled
|
||||
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -76,6 +84,7 @@ class SubEditActivity : BaseActivity() {
|
||||
subItem.remarks = binding.etRemarks.text.toString()
|
||||
subItem.url = binding.etUrl.text.toString()
|
||||
subItem.enabled = binding.chkEnable.isChecked
|
||||
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
|
||||
|
||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||
toast(R.string.sub_setting_remarks)
|
||||
@@ -130,4 +139,5 @@ class SubEditActivity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ class SubSettingActivity : BaseActivity() {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
@@ -90,7 +90,7 @@ class TaskerActivity : BaseActivity() {
|
||||
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ class UserAssetActivity : BaseActivity() {
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_user_asset_setting)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_CONFIG
|
||||
import com.v2ray.ang.AppConfig.HTTPS_PROTOCOL
|
||||
import com.v2ray.ang.AppConfig.HTTP_PROTOCOL
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.*
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
|
||||
@@ -20,6 +22,7 @@ import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.removeWhiteSpace
|
||||
import com.v2ray.ang.extension.toast
|
||||
|
||||
object AngConfigManager {
|
||||
@@ -432,6 +435,28 @@ object AngConfigManager {
|
||||
queryParam["security"] ?: "", allowInsecure,
|
||||
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx
|
||||
)
|
||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
|
||||
if (uri.rawQuery != null) {
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||
|
||||
config.outboundBean?.settings?.let { wireguard ->
|
||||
wireguard.secretKey = uri.userInfo
|
||||
wireguard.address =
|
||||
(queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
|
||||
.split(",")
|
||||
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
|
||||
wireguard.peers?.get(0)?.endpoint = "${uri.idnHost}:${uri.port}"
|
||||
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: WIREGUARD_LOCAL_MTU)
|
||||
wireguard.reserved =
|
||||
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
|
||||
.map { it.toInt() }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config == null) {
|
||||
return R.string.toast_incorrect_protocol
|
||||
@@ -443,7 +468,8 @@ object AngConfigManager {
|
||||
?.getServerAddress() == removedSelectedServer.getProxyOutbound()
|
||||
?.getServerAddress() &&
|
||||
config.getProxyOutbound()
|
||||
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
|
||||
?.getServerPort() == removedSelectedServer.getProxyOutbound()
|
||||
?.getServerPort()
|
||||
) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
|
||||
}
|
||||
@@ -571,7 +597,11 @@ object AngConfigManager {
|
||||
try {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
|
||||
val outbound = config.getProxyOutbound() ?: return ""
|
||||
val streamSetting = outbound.streamSettings ?: return ""
|
||||
val streamSetting =
|
||||
outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||
if (config.configType != EConfigType.WIREGUARD) {
|
||||
if (outbound.streamSettings == null) return ""
|
||||
}
|
||||
return config.configType.protocolScheme + when (config.configType) {
|
||||
EConfigType.VMESS -> {
|
||||
val vmessQRCode = VmessQRCode()
|
||||
@@ -600,7 +630,8 @@ object AngConfigManager {
|
||||
Utils.encode(json)
|
||||
}
|
||||
|
||||
EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
|
||||
EConfigType.CUSTOM -> ""
|
||||
|
||||
EConfigType.SHADOWSOCKS -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
val pw =
|
||||
@@ -617,10 +648,13 @@ object AngConfigManager {
|
||||
EConfigType.SOCKS -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
val pw =
|
||||
Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
|
||||
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
|
||||
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
|
||||
else
|
||||
":"
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
pw,
|
||||
Utils.encode(pw),
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort()
|
||||
)
|
||||
@@ -672,7 +706,8 @@ object AngConfigManager {
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
|
||||
}
|
||||
}
|
||||
dicQuery["type"] = streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||
dicQuery["type"] =
|
||||
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||
|
||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||
when (streamSetting.network) {
|
||||
@@ -733,6 +768,37 @@ object AngConfigManager {
|
||||
)
|
||||
url + query + remark
|
||||
}
|
||||
|
||||
EConfigType.WIREGUARD -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
|
||||
val dicQuery = HashMap<String, String>()
|
||||
dicQuery["publickey"] =
|
||||
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
|
||||
if (outbound.settings?.reserved != null) {
|
||||
dicQuery["reserved"] = Utils.urlEncode(
|
||||
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
|
||||
.toString()
|
||||
)}
|
||||
dicQuery["address"] = Utils.urlEncode(
|
||||
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
|
||||
.toString()
|
||||
)
|
||||
if (outbound.settings?.mtu != null) {
|
||||
dicQuery["mtu"] = outbound.settings?.mtu.toString()
|
||||
}
|
||||
val query = "?" + dicQuery.toList().joinToString(
|
||||
separator = "&",
|
||||
transform = { it.first + "=" + it.second })
|
||||
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
Utils.urlEncode(outbound.getPassword().toString()),
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort()
|
||||
)
|
||||
url + query + remark
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -858,17 +924,19 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
val removedSelectedServer =
|
||||
if (!TextUtils.isEmpty(subid) && !append) {
|
||||
MmkvManager.decodeServerConfig(mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: "")?.let {
|
||||
if (it.subscriptionId == subid) {
|
||||
return@let it
|
||||
}
|
||||
return@let null
|
||||
if (!TextUtils.isEmpty(subid) && !append) {
|
||||
MmkvManager.decodeServerConfig(
|
||||
mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: ""
|
||||
)?.let {
|
||||
if (it.subscriptionId == subid) {
|
||||
return@let it
|
||||
}
|
||||
} else {
|
||||
null
|
||||
return@let null
|
||||
}
|
||||
if(!append) {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (!append) {
|
||||
MmkvManager.removeServerViaSubid(subid)
|
||||
}
|
||||
// var servers = server
|
||||
@@ -908,4 +976,24 @@ object AngConfigManager {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun appendCustomConfigServer(server: String?, subid: String): Int {
|
||||
if (server == null) {
|
||||
return 0
|
||||
}
|
||||
if (server.contains("inbounds")
|
||||
&& server.contains("outbounds")
|
||||
&& server.contains("routing")
|
||||
) {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.remarks = System.currentTimeMillis().toString()
|
||||
config.subscriptionId = subid
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, server)
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.webkit.URLUtil
|
||||
@@ -178,7 +179,7 @@ object Utils {
|
||||
//CIDR
|
||||
if (addr.indexOf("/") > 0) {
|
||||
val arr = addr.split("/")
|
||||
if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) {
|
||||
if (arr.count() == 2 && Integer.parseInt(arr[1]) > -1) {
|
||||
addr = arr[0]
|
||||
}
|
||||
}
|
||||
@@ -282,7 +283,7 @@ object Utils {
|
||||
|
||||
fun urlDecode(url: String): String {
|
||||
return try {
|
||||
URLDecoder.decode(URLDecoder.decode(url), "utf-8")
|
||||
URLDecoder.decode(url, "UTF-8")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
url
|
||||
@@ -317,6 +318,11 @@ object Utils {
|
||||
return extDir.absolutePath
|
||||
}
|
||||
|
||||
fun getDeviceIdForXUDPBaseKey(): String {
|
||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8"))
|
||||
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
||||
}
|
||||
|
||||
fun getUrlContext(url: String, timeout: Int): String {
|
||||
var result: String
|
||||
var conn: HttpURLConnection? = null
|
||||
@@ -356,11 +362,11 @@ object Utils {
|
||||
|
||||
fun getDarkModeStatus(context: Context): Boolean {
|
||||
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
|
||||
return mode == UI_MODE_NIGHT_YES
|
||||
return mode != UI_MODE_NIGHT_NO
|
||||
}
|
||||
|
||||
fun getIpv6Address(address: String): String {
|
||||
return if (isIpv6Address(address)) {
|
||||
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
||||
String.format("[%s]", address)
|
||||
} else {
|
||||
address
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.google.gson.*
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ERoutingMode
|
||||
@@ -32,12 +34,12 @@ object V2rayConfigUtil {
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
Log.d(ANG_PACKAGE, customConfig)
|
||||
//Log.d(ANG_PACKAGE, customConfig)
|
||||
return Result(true, customConfig)
|
||||
}
|
||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||
val result = getV2rayNonCustomConfig(context, outbound)
|
||||
Log.d(ANG_PACKAGE, result.content)
|
||||
//Log.d(ANG_PACKAGE, result.content)
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -64,7 +66,7 @@ object V2rayConfigUtil {
|
||||
|
||||
inbounds(v2rayConfig)
|
||||
|
||||
httpRequestObject(outbound)
|
||||
updateOutboundWithGlobalSettings(outbound)
|
||||
|
||||
v2rayConfig.outbounds[0] = outbound
|
||||
|
||||
@@ -399,10 +401,49 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
|
||||
private fun updateOutboundWithGlobalSettings(outbound: V2rayConfig.OutboundBean): Boolean {
|
||||
try {
|
||||
var muxEnabled = settingsStorage?.decodeBool(AppConfig.PREF_MUX_ENABLED, false)
|
||||
val protocol = outbound.protocol
|
||||
if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.WIREGUARD.name, true)
|
||||
) {
|
||||
muxEnabled = false
|
||||
} else if (protocol.equals(EConfigType.VLESS.name, true)
|
||||
&& outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.isNotEmpty() == true
|
||||
) {
|
||||
muxEnabled = false
|
||||
}
|
||||
if (muxEnabled == true) {
|
||||
outbound.mux?.enabled = true
|
||||
outbound.mux?.concurrency =
|
||||
settingsStorage?.decodeInt(AppConfig.PREF_MUX_CONCURRENCY) ?: 8
|
||||
outbound.mux?.xudpConcurrency =
|
||||
settingsStorage?.decodeInt(AppConfig.PREF_MUX_XUDP_CONCURRENCY) ?: 8
|
||||
outbound.mux?.xudpProxyUDP443 =
|
||||
settingsStorage?.decodeString(AppConfig.PREF_MUX_XUDP_QUIC) ?: "reject"
|
||||
} else {
|
||||
outbound.mux?.enabled = false
|
||||
outbound.mux?.concurrency = -1
|
||||
}
|
||||
|
||||
if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
var localTunAddr = if (outbound.settings?.address == null) {
|
||||
listOf(WIREGUARD_LOCAL_ADDRESS_V4, WIREGUARD_LOCAL_ADDRESS_V6)
|
||||
} else {
|
||||
outbound.settings?.address as List<*>
|
||||
}
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) != true) {
|
||||
localTunAddr = listOf(localTunAddr.first())
|
||||
}
|
||||
outbound.settings?.address = localTunAddr
|
||||
}
|
||||
|
||||
if (outbound.streamSettings?.network == DEFAULT_NETWORK
|
||||
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
|
||||
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP
|
||||
) {
|
||||
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
|
||||
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.v2ray.ang.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ArrayAdapter
|
||||
@@ -24,12 +25,28 @@ import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val mainStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_MAIN,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val serverRawStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SERVER_RAW,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val settingsStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SETTING,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
|
||||
var serverList = MmkvManager.decodeServerList()
|
||||
var subscriptionId: String = ""
|
||||
var keywordFilter: String = ""
|
||||
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")!!
|
||||
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")!!
|
||||
private set
|
||||
val serversCache = mutableListOf<ServersCache>()
|
||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||
@@ -40,7 +57,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
fun startListenBroadcast() {
|
||||
isRunning.value = false
|
||||
getApplication<AngApplication>().registerReceiver(mMsgReceiver, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
||||
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)
|
||||
)
|
||||
}
|
||||
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
|
||||
}
|
||||
|
||||
@@ -62,7 +90,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
serverList.remove(guid)
|
||||
MmkvManager.removeServer(guid)
|
||||
val index = getPosition(guid)
|
||||
if(index >= 0){
|
||||
if (index >= 0) {
|
||||
serversCache.removeAt(index)
|
||||
}
|
||||
}
|
||||
@@ -75,7 +103,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, server)
|
||||
serverList.add(0, key)
|
||||
serversCache.add(0, ServersCache(key,config))
|
||||
serversCache.add(0, ServersCache(key, config))
|
||||
}
|
||||
|
||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||
@@ -115,7 +143,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
|
||||
launch(Dispatchers.Main) {
|
||||
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
|
||||
updateListAction.value = getPosition(item.guid)
|
||||
updateListAction.value = getPosition(item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,12 +156,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
MmkvManager.clearAllTestDelayResults()
|
||||
updateListAction.value = -1 // update all
|
||||
|
||||
val serversCopy = serversCache.toList() // Create a copy of the list
|
||||
|
||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
|
||||
for (item in serversCache) {
|
||||
for (item in serversCopy) {
|
||||
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid)
|
||||
if (config.status) {
|
||||
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, Pair(item.guid, config.content))
|
||||
MessageUtil.sendMsg2TestService(
|
||||
getApplication(),
|
||||
AppConfig.MSG_MEASURE_CONFIG,
|
||||
Pair(item.guid, config.content)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +177,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
|
||||
}
|
||||
|
||||
fun filterConfig(context :Context) {
|
||||
fun filterConfig(context: Context) {
|
||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||
val listId = subscriptions.map { it.first }.toList().toMutableList()
|
||||
val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList()
|
||||
@@ -155,7 +189,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context))
|
||||
ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>( context, android.R.layout.simple_spinner_dropdown_item, listRemarks)
|
||||
ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>(
|
||||
context,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
listRemarks
|
||||
)
|
||||
ivBinding.spSubscriptionId.setSelection(checkedItem)
|
||||
ivBinding.etKeyword.text = Utils.getEditable(keywordFilter)
|
||||
val builder = AlertDialog.Builder(context).setView(ivBinding.root)
|
||||
@@ -169,6 +207,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
subscriptions[position].first
|
||||
}
|
||||
keywordFilter = ivBinding.etKeyword.text.toString()
|
||||
settingsStorage?.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
|
||||
settingsStorage?.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
|
||||
reloadServerList()
|
||||
|
||||
dialogInterface?.dismiss()
|
||||
@@ -193,7 +233,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
// }.show()
|
||||
}
|
||||
|
||||
fun getPosition(guid: String) : Int {
|
||||
fun getPosition(guid: String): Int {
|
||||
serversCache.forEachIndexed { index, it ->
|
||||
if (it.guid == guid)
|
||||
return index
|
||||
@@ -206,20 +246,24 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
serversCache.forEachIndexed { index, it ->
|
||||
val outbound = it.config.getProxyOutbound()
|
||||
serversCache.forEachIndexed { index2, it2 ->
|
||||
if(index2 > index){
|
||||
if (index2 > index) {
|
||||
val outbound2 = it2.config.getProxyOutbound()
|
||||
if( outbound == outbound2 && !deleteServer.contains(it2.guid))
|
||||
{
|
||||
if (outbound == outbound2 && !deleteServer.contains(it2.guid)) {
|
||||
deleteServer.add(it2.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(it in deleteServer){
|
||||
for (it in deleteServer) {
|
||||
MmkvManager.removeServer(it)
|
||||
}
|
||||
reloadServerList()
|
||||
getApplication<AngApplication>().toast(getApplication<AngApplication>().getString(R.string.title_del_duplicate_config_count, deleteServer.count()))
|
||||
getApplication<AngApplication>().toast(
|
||||
getApplication<AngApplication>().getString(
|
||||
R.string.title_del_duplicate_config_count,
|
||||
deleteServer.count()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||
@@ -228,23 +272,29 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
AppConfig.MSG_STATE_RUNNING -> {
|
||||
isRunning.value = true
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_NOT_RUNNING -> {
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||
getApplication<AngApplication>().toast(R.string.toast_services_success)
|
||||
isRunning.value = true
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_START_FAILURE -> {
|
||||
getApplication<AngApplication>().toast(R.string.toast_services_failure)
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_DELAY_SUCCESS -> {
|
||||
updateTestResultAction.value = intent.getStringExtra("content")
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||
val resultPair = intent.getSerializableExtra("content") as Pair<String, Long>
|
||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||
|
||||
@@ -39,7 +39,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
AppConfig.PREF_ROUTING_MODE,
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT, -> {
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT,
|
||||
AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,
|
||||
AppConfig.PREF_MUX_XUDP_QUIC, -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
||||
}
|
||||
AppConfig.PREF_SPEED_ENABLED,
|
||||
@@ -51,12 +53,18 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
AppConfig.PREF_PER_APP_PROXY,
|
||||
AppConfig.PREF_BYPASS_APPS,
|
||||
AppConfig.PREF_CONFIRM_REMOVE,
|
||||
AppConfig.PREF_START_SCAN_IMMEDIATE, -> {
|
||||
AppConfig.PREF_START_SCAN_IMMEDIATE,
|
||||
AppConfig.SUBSCRIPTION_AUTO_UPDATE,
|
||||
AppConfig.PREF_MUX_ENABLED, -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
AppConfig.PREF_SNIFFING_ENABLED -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
|
||||
}
|
||||
AppConfig.PREF_MUX_CONCURRENCY,
|
||||
AppConfig.PREF_MUX_XUDP_CONCURRENCY -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getString(key, "8")?.toIntOrNull() ?: 8)
|
||||
}
|
||||
AppConfig.PREF_PER_APP_PROXY_SET -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 526 B |
|
After Width: | Height: | Size: 651 B |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 476 B |
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 984 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -4,6 +4,6 @@
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||
</vector>
|
||||
@@ -4,6 +4,6 @@
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:fillColor="#000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,5a3,3 0,0 0,3 3h4L15,1Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M2,17L10,17"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,13L6,21"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
@@ -4,6 +4,6 @@
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:fillColor="#000"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
|
||||
<path
|
||||
android:fillColor="@color/colorBg"
|
||||
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
|
||||
<path
|
||||
android:fillColor="@color/colorText"
|
||||
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
|
||||
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
|
||||
<path
|
||||
android:fillColor="@color/colorText"
|
||||
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
|
||||
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
|
||||
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:drawable="@drawable/ic_shortcut_background"
|
||||
android:left="2dp"
|
||||
android:top="2dp"
|
||||
android:right="2dp"
|
||||
android:bottom="2dp" />
|
||||
|
||||
<item
|
||||
android:drawable="@drawable/ic_stat_name_black"
|
||||
android:left="12dp"
|
||||
android:top="12dp"
|
||||
android:right="12dp"
|
||||
android:bottom="12dp" />
|
||||
|
||||
</layer-list>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#009963"/>
|
||||
<solid android:color="@color/color_fab_orange"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||
</shape>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/colorUnselected"/>
|
||||
<solid android:color="@color/color_switch_fab_grey"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||
</shape>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</vector>
|
||||
@@ -5,14 +5,14 @@
|
||||
android:viewportHeight="48">
|
||||
|
||||
<path
|
||||
android:fillColor="#999999"
|
||||
android:fillColor="#050505"
|
||||
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
|
||||
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
|
||||
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
|
||||
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
||||
@@ -2,6 +2,6 @@
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/colorUnselected" />
|
||||
<solid android:color="@color/colorBg" />
|
||||
<size android:width="44dp" android:height="44dp" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -3,7 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
tools:context=".ui.MainActivity">
|
||||
|
||||
<ProgressBar
|
||||
|
||||