Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a9d315e62 | ||
|
|
c42aa93bf7 | ||
|
|
a7664f03aa | ||
|
|
fa341c9a5a | ||
|
|
a15ab4759e | ||
|
|
b8939763d4 | ||
|
|
51adca8568 | ||
|
|
f646eff048 | ||
|
|
fee0a016d8 | ||
|
|
f040fa5c08 | ||
|
|
7f3c6b4665 | ||
|
|
8b806fe0be | ||
|
|
b37d8c2369 | ||
|
|
b5451e9d3d | ||
|
|
b2235d4c38 | ||
|
|
c8ba5d727e | ||
|
|
a3de44cd0a | ||
|
|
214d9e1c53 | ||
|
|
92900c3f74 | ||
|
|
e17e566daa | ||
|
|
df4e232087 | ||
|
|
828a39331b | ||
|
|
a0223a3eee | ||
|
|
7f6a526b25 | ||
|
|
9983ea25d2 | ||
|
|
47166b937f | ||
|
|
0a09966e81 | ||
|
|
7ec34e934e | ||
|
|
f2b03e7492 | ||
|
|
5208bd62c5 | ||
|
|
d9f0854c27 | ||
|
|
6be125b5cb | ||
|
|
c884c098fd | ||
|
|
f77fe05c92 | ||
|
|
f3bfa8ceba | ||
|
|
ae7d9d87d2 | ||
|
|
1652040c1c | ||
|
|
818b7cdff4 | ||
|
|
48ce359d2d | ||
|
|
e115bf0c6d | ||
|
|
0415b60ba5 | ||
|
|
4570fdb05f | ||
|
|
9d109e7ca9 | ||
|
|
2574553180 | ||
|
|
66e77d50bd | ||
|
|
253bd793d7 | ||
|
|
be30de6728 | ||
|
|
a1455bbb1c | ||
|
|
1ac19ae3e9 | ||
|
|
6e6ca209df | ||
|
|
52699967cd | ||
|
|
146d20ce86 | ||
|
|
b6959b5990 | ||
|
|
06649df8b1 | ||
|
|
0174ed9082 | ||
|
|
adabb281b1 | ||
|
|
63e710d1ab | ||
|
|
509a568446 | ||
|
|
6fd94b53f0 | ||
|
|
164412fa34 | ||
|
|
514ca0810e | ||
|
|
7582f86482 | ||
|
|
4b4c46e5ae | ||
|
|
162e156b33 | ||
|
|
bc7d1971ef | ||
|
|
bbdee92f37 | ||
|
|
cf9e830cc7 | ||
|
|
804e425a87 | ||
|
|
cc21383928 | ||
|
|
413f4efd69 | ||
|
|
de69605eff | ||
|
|
9338ba3525 | ||
|
|
322b6ec615 | ||
|
|
304232d029 | ||
|
|
f80c3bfe07 | ||
|
|
bb0a62fc8b | ||
|
|
5bfdca6cd9 | ||
|
|
447e712a9d | ||
|
|
8bb03f189d | ||
|
|
3b0554cd9b | ||
|
|
858101b0d9 | ||
|
|
a7cf8bee28 | ||
|
|
af1ec7bea9 | ||
|
|
002bf7ef22 | ||
|
|
6919e2336d | ||
|
|
5a5bd22073 | ||
|
|
a726f00f35 | ||
|
|
79297f8a42 | ||
|
|
e3f70ac253 | ||
|
|
838b346fcc | ||
|
|
eba9545ccf | ||
|
|
890ade9495 | ||
|
|
518ef1e0ec | ||
|
|
bdcecfca72 | ||
|
|
1363846ac4 | ||
|
|
30347546a2 | ||
|
|
ac7eb28e91 | ||
|
|
748405473b | ||
|
|
1080390bed | ||
|
|
c48725c7dd | ||
|
|
a5287dbadc | ||
|
|
ee5a3b0dd9 | ||
|
|
3d001541e5 | ||
|
|
b376b229b9 | ||
|
|
33b6203978 | ||
|
|
2d803e009c | ||
|
|
2ddbe38781 | ||
|
|
96181a2b8d | ||
|
|
8308b8eaf2 | ||
|
|
00f26ff529 | ||
|
|
49dcdf3ae5 | ||
|
|
409b431d1c | ||
|
|
6da988e3db | ||
|
|
fd8f8306ee | ||
|
|
74b342f5c6 | ||
|
|
2504ec79ee | ||
|
|
3e7b211b17 | ||
|
|
13d5514a4c | ||
|
|
f0f9da0f1b | ||
|
|
f6d2c5f473 | ||
|
|
6e8dd5b250 | ||
|
|
f4779bc50c | ||
|
|
432baf262d | ||
|
|
a1d68fcde3 | ||
|
|
e053db3dff | ||
|
|
f624bd651e | ||
|
|
84964c7f91 | ||
|
|
96f56b468e | ||
|
|
bdc3212f38 | ||
|
|
508ddf6df2 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
|
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
|
||||||
gomobile init
|
gomobile init
|
||||||
go mod tidy -v
|
go mod tidy -v
|
||||||
gomobile bind -v -androidapi 19 -ldflags='-s -w' ./
|
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
|
||||||
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
[](https://developer.android.com/about/versions/lollipop)
|
[](https://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
|
|||||||
@@ -11,9 +11,22 @@ android {
|
|||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 558
|
versionCode = 585
|
||||||
versionName = "1.8.22"
|
versionName = "1.8.39"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
splits {
|
||||||
|
abi {
|
||||||
|
isEnable = true
|
||||||
|
include(
|
||||||
|
"arm64-v8a",
|
||||||
|
"armeabi-v7a",
|
||||||
|
"x86_64",
|
||||||
|
"x86"
|
||||||
|
)
|
||||||
|
isUniversalApk = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -41,17 +54,10 @@ android {
|
|||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
splits {
|
|
||||||
abi {
|
|
||||||
isEnable = true
|
|
||||||
isUniversalApk = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
val variant = this
|
val variant = this
|
||||||
val versionCodes =
|
val versionCodes =
|
||||||
mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4)
|
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
|
||||||
|
|
||||||
variant.outputs
|
variant.outputs
|
||||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
@@ -59,15 +65,12 @@ android {
|
|||||||
val abi = if (output.getFilter("ABI") != null)
|
val abi = if (output.getFilter("ABI") != null)
|
||||||
output.getFilter("ABI")
|
output.getFilter("ABI")
|
||||||
else
|
else
|
||||||
"all"
|
"universal"
|
||||||
|
|
||||||
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
||||||
if(versionCodes.containsKey(abi))
|
if (versionCodes.containsKey(abi)) {
|
||||||
{
|
|
||||||
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,47 +80,53 @@ android {
|
|||||||
viewBinding = true
|
viewBinding = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation(libs.junit)
|
||||||
|
|
||||||
|
implementation(libs.flexbox)
|
||||||
// Androidx
|
// Androidx
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation(libs.constraintlayout)
|
||||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
implementation(libs.legacy.support.v4)
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation(libs.appcompat)
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation(libs.material)
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
implementation(libs.cardview)
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation(libs.preference.ktx)
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
implementation(libs.recyclerview)
|
||||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
implementation(libs.fragment.ktx)
|
||||||
implementation("androidx.multidex:multidex:2.0.1")
|
implementation(libs.multidex)
|
||||||
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
|
implementation(libs.viewpager2)
|
||||||
|
|
||||||
// Androidx ktx
|
// Androidx ktx
|
||||||
implementation("androidx.activity:activity-ktx:1.9.0")
|
implementation(libs.activity.ktx)
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
implementation(libs.lifecycle.viewmodel.ktx)
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
implementation(libs.lifecycle.livedata.ktx)
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
implementation(libs.lifecycle.runtime.ktx)
|
||||||
|
|
||||||
//kotlin
|
//kotlin
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
|
implementation(libs.kotlin.reflect)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
|
||||||
implementation("com.tencent:mmkv-static:1.3.4")
|
implementation(libs.mmkv.static)
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation(libs.gson)
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation(libs.rxjava)
|
||||||
implementation("io.reactivex:rxandroid:1.2.1")
|
implementation(libs.rxandroid)
|
||||||
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar")
|
implementation(libs.rxpermissions)
|
||||||
implementation("me.drakeet.support:toastcompat:1.1.0")
|
implementation(libs.toastcompat)
|
||||||
implementation("com.blacksquircle.ui:editorkit:2.9.0")
|
implementation(libs.editorkit)
|
||||||
implementation("com.blacksquircle.ui:language-base:2.9.0")
|
implementation(libs.language.base)
|
||||||
implementation("com.blacksquircle.ui:language-json:2.9.0")
|
implementation(libs.language.json)
|
||||||
implementation("io.github.g00fy2.quickie:quickie-bundled:1.9.0")
|
implementation(libs.quickie.bundled)
|
||||||
implementation("com.google.zxing:core:3.5.3")
|
implementation(libs.core)
|
||||||
|
implementation(libs.work.runtime.ktx)
|
||||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
implementation(libs.work.multiprocess)
|
||||||
implementation("androidx.work:work-multiprocess:2.8.1")
|
|
||||||
}
|
}
|
||||||
@@ -8,17 +8,28 @@
|
|||||||
android:smallScreens="true"
|
android:smallScreens="true"
|
||||||
android:normalScreens="true"
|
android:normalScreens="true"
|
||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
android:xlargeScreens="true"/>
|
android:xlargeScreens="true" />
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
|
<uses-sdk
|
||||||
|
android:minSdkVersion="21"
|
||||||
|
tools:overrideLibrary="com.blacksquircle.ui.editorkit" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
android:name="android.hardware.camera"
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera.autofocus"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
<uses-permission
|
||||||
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
@@ -31,7 +42,7 @@
|
|||||||
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||||
android:minSdkVersion="34" />
|
android:minSdkVersion="34" />
|
||||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
|
||||||
|
|
||||||
@@ -53,12 +64,14 @@
|
|||||||
android:theme="@style/AppThemeDayNight.NoActionBar">
|
android:theme="@style/AppThemeDayNight.NoActionBar">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<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>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
@@ -119,12 +132,14 @@
|
|||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="v2rayng"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:host="install-config"/>
|
|
||||||
<data android:host="install-sub"/>
|
<data android:scheme="v2rayng" />
|
||||||
|
<data android:host="install-config" />
|
||||||
|
<data android:host="install-sub" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
@@ -150,7 +165,8 @@
|
|||||||
android:value="vpn" />
|
android:value="vpn" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".service.V2RayProxyOnlyService"
|
<service
|
||||||
|
android:name=".service.V2RayProxyOnlyService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:foregroundServiceType="specialUse"
|
android:foregroundServiceType="specialUse"
|
||||||
@@ -160,10 +176,11 @@
|
|||||||
android:value="proxy" />
|
android:value="proxy" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".service.V2RayTestService"
|
<service
|
||||||
|
android:name=".service.V2RayTestService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon"
|
||||||
</service>
|
/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -227,6 +244,16 @@
|
|||||||
|
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.cache"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/cache_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
geosite:cn,
|
geosite:cn
|
||||||
geosite:geolocation-cn
|
|
||||||
@@ -81,7 +81,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {},
|
"settings": {
|
||||||
|
"domainStrategy": "UseIP"
|
||||||
|
},
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package com.v2ray.ang.helper;
|
package com.v2ray.ang.helper;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
|
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
|
||||||
@@ -36,7 +36,6 @@ public interface ItemTouchHelperAdapter {
|
|||||||
* @param fromPosition The start position of the moved item.
|
* @param fromPosition The start position of the moved item.
|
||||||
* @param toPosition Then resolved position of the moved item.
|
* @param toPosition Then resolved position of the moved item.
|
||||||
* @return True if the item was moved to the new adapter position.
|
* @return True if the item was moved to the new adapter position.
|
||||||
*
|
|
||||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
||||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
||||||
*/
|
*/
|
||||||
@@ -52,7 +51,6 @@ public interface ItemTouchHelperAdapter {
|
|||||||
* adjusting the underlying data to reflect this removal.
|
* adjusting the underlying data to reflect this removal.
|
||||||
*
|
*
|
||||||
* @param position The position of the item dismissed.
|
* @param position The position of the item dismissed.
|
||||||
*
|
|
||||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
||||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
package com.v2ray.ang.helper;
|
package com.v2ray.ang.helper;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package com.v2ray.ang
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.WorkManager
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
class AngApplication : MultiDexApplication(), Configuration.Provider {
|
class AngApplication : MultiDexApplication() {
|
||||||
companion object {
|
companion object {
|
||||||
//const val PREF_LAST_VERSION = "pref_last_version"
|
//const val PREF_LAST_VERSION = "pref_last_version"
|
||||||
lateinit var application: AngApplication
|
lateinit var application: AngApplication
|
||||||
@@ -17,8 +18,9 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
|
|||||||
application = this
|
application = this
|
||||||
}
|
}
|
||||||
|
|
||||||
//var firstRun = false
|
private val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||||
// private set
|
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
||||||
|
.build()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@@ -34,11 +36,7 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
|
|||||||
MMKV.initialize(this)
|
MMKV.initialize(this)
|
||||||
|
|
||||||
Utils.setNightMode(application)
|
Utils.setNightMode(application)
|
||||||
}
|
// Initialize WorkManager with the custom configuration
|
||||||
|
WorkManager.initialize(this, workManagerConfiguration)
|
||||||
override fun getWorkManagerConfiguration(): Configuration {
|
|
||||||
return Configuration.Builder()
|
|
||||||
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
package com.v2ray.ang
|
package com.v2ray.ang
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* App Config Const
|
|
||||||
*/
|
|
||||||
object AppConfig {
|
object AppConfig {
|
||||||
|
|
||||||
|
/** The application's package name. */
|
||||||
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
|
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
|
||||||
|
|
||||||
|
/** Directory names used in the app's file system. */
|
||||||
const val DIR_ASSETS = "assets"
|
const val DIR_ASSETS = "assets"
|
||||||
const val DIR_BACKUPS = "backups"
|
const val DIR_BACKUPS = "backups"
|
||||||
|
|
||||||
// legacy
|
/** Legacy configuration keys. */
|
||||||
const val ANG_CONFIG = "ang_config"
|
const val ANG_CONFIG = "ang_config"
|
||||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||||
|
|
||||||
// Preferences mapped to MMKV
|
/** Preferences mapped to MMKV storage. */
|
||||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||||
|
const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled"
|
||||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||||
@@ -22,69 +24,73 @@ object AppConfig {
|
|||||||
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
||||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||||
|
|
||||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||||
|
|
||||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||||
const val PREF_MUX_CONCURRENCY = "pref_mux_concurency"
|
const val PREF_MUX_CONCURRENCY = "pref_mux_concurrency"
|
||||||
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency"
|
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurrency"
|
||||||
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
|
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
|
||||||
|
|
||||||
const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
|
const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
|
||||||
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
|
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
|
||||||
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
|
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
|
||||||
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
|
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
|
||||||
|
|
||||||
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
|
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
|
||||||
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
|
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
|
||||||
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
|
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // Default is 24 hours
|
||||||
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
|
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
|
||||||
|
|
||||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||||
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
||||||
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
|
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
|
||||||
const val PREF_LANGUAGE = "pref_language"
|
const val PREF_LANGUAGE = "pref_language"
|
||||||
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
|
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
|
||||||
|
|
||||||
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
|
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
|
||||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||||
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||||
const val PREF_SOCKS_PORT = "pref_socks_port"
|
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||||
const val PREF_HTTP_PORT = "pref_http_port"
|
const val PREF_HTTP_PORT = "pref_http_port"
|
||||||
|
|
||||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||||
|
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
||||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||||
const val PREF_MODE = "pref_mode"
|
const val PREF_MODE = "pref_mode"
|
||||||
|
|
||||||
|
/** Cache keys. */
|
||||||
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
|
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
|
||||||
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
|
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
|
||||||
|
|
||||||
//Preferences mapped to MMKV End
|
/** Protocol identifiers. */
|
||||||
|
|
||||||
const val PROTOCOL_HTTP: String = "http://"
|
const val PROTOCOL_HTTP: String = "http://"
|
||||||
const val PROTOCOL_HTTPS: String = "https://"
|
const val PROTOCOL_HTTPS: String = "https://"
|
||||||
const val PROTOCOL_FREEDOM: String = "freedom"
|
const val PROTOCOL_FREEDOM: String = "freedom"
|
||||||
|
|
||||||
|
/** Broadcast actions. */
|
||||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||||
|
|
||||||
|
/** Tasker extras. */
|
||||||
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
|
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
|
||||||
const val TASKER_EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
|
const val TASKER_EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
|
||||||
const val TASKER_EXTRA_BUNDLE_SWITCH = "tasker_extra_bundle_switch"
|
const val TASKER_EXTRA_BUNDLE_SWITCH = "tasker_extra_bundle_switch"
|
||||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||||
const val TASKER_DEFAULT_GUID = "Default"
|
const val TASKER_DEFAULT_GUID = "Default"
|
||||||
|
|
||||||
|
/** Tags for different proxy modes. */
|
||||||
const val TAG_PROXY = "proxy"
|
const val TAG_PROXY = "proxy"
|
||||||
const val TAG_DIRECT = "direct"
|
const val TAG_DIRECT = "direct"
|
||||||
const val TAG_BLOCKED = "block"
|
const val TAG_BLOCKED = "block"
|
||||||
const val TAG_FRAGMENT = "fragment"
|
const val TAG_FRAGMENT = "fragment"
|
||||||
|
|
||||||
|
/** Network-related constants. */
|
||||||
|
const val UPLINK = "uplink"
|
||||||
|
const val DOWNLINK = "downlink"
|
||||||
|
|
||||||
|
/** URLs for various resources. */
|
||||||
const val androidpackagenamelistUrl =
|
const val androidpackagenamelistUrl =
|
||||||
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||||
const val v2rayCustomRoutingListUrl =
|
const val v2rayCustomRoutingListUrl =
|
||||||
@@ -96,11 +102,15 @@ object AppConfig {
|
|||||||
const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
|
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 TgChannelUrl = "https://t.me/github_2dust"
|
const val TgChannelUrl = "https://t.me/github_2dust"
|
||||||
|
const val DelayTestUrl = "https://www.gstatic.com/generate_204"
|
||||||
|
const val DelayTestUrl2 = "https://www.google.com/generate_204"
|
||||||
|
|
||||||
|
/** DNS server addresses. */
|
||||||
const val DNS_PROXY = "1.1.1.1"
|
const val DNS_PROXY = "1.1.1.1"
|
||||||
const val DNS_DIRECT = "223.5.5.5"
|
const val DNS_DIRECT = "223.5.5.5"
|
||||||
const val DNS_VPN = "1.1.1.1"
|
const val DNS_VPN = "1.1.1.1"
|
||||||
|
|
||||||
|
/** Ports and addresses for various services. */
|
||||||
const val PORT_LOCAL_DNS = "10853"
|
const val PORT_LOCAL_DNS = "10853"
|
||||||
const val PORT_SOCKS = "10808"
|
const val PORT_SOCKS = "10808"
|
||||||
const val PORT_HTTP = "10809"
|
const val PORT_HTTP = "10809"
|
||||||
@@ -108,6 +118,7 @@ object AppConfig {
|
|||||||
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
||||||
const val WIREGUARD_LOCAL_MTU = "1420"
|
const val WIREGUARD_LOCAL_MTU = "1420"
|
||||||
|
|
||||||
|
/** Message constants for communication. */
|
||||||
const val MSG_REGISTER_CLIENT = 1
|
const val MSG_REGISTER_CLIENT = 1
|
||||||
const val MSG_STATE_RUNNING = 11
|
const val MSG_STATE_RUNNING = 11
|
||||||
const val MSG_STATE_NOT_RUNNING = 12
|
const val MSG_STATE_NOT_RUNNING = 12
|
||||||
@@ -123,4 +134,19 @@ object AppConfig {
|
|||||||
const val MSG_MEASURE_CONFIG = 7
|
const val MSG_MEASURE_CONFIG = 7
|
||||||
const val MSG_MEASURE_CONFIG_SUCCESS = 71
|
const val MSG_MEASURE_CONFIG_SUCCESS = 71
|
||||||
const val MSG_MEASURE_CONFIG_CANCEL = 72
|
const val MSG_MEASURE_CONFIG_CANCEL = 72
|
||||||
|
|
||||||
|
/** Notification channel IDs and names. */
|
||||||
|
const val RAY_NG_CHANNEL_ID = "RAY_NG_M_CH_ID"
|
||||||
|
const val RAY_NG_CHANNEL_NAME = "V2rayNG Background Service"
|
||||||
|
const val SUBSCRIPTION_UPDATE_CHANNEL = "subscription_update_channel"
|
||||||
|
const val SUBSCRIPTION_UPDATE_CHANNEL_NAME = "Subscription Update Service"
|
||||||
|
|
||||||
|
/** Protocols Scheme **/
|
||||||
|
const val VMESS = "vmess://"
|
||||||
|
const val CUSTOM = ""
|
||||||
|
const val SHADOWSOCKS = "ss://"
|
||||||
|
const val SOCKS = "socks://"
|
||||||
|
const val VLESS = "vless://"
|
||||||
|
const val TROJAN = "trojan://"
|
||||||
|
const val WIREGUARD = "wireguard://"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.v2ray.ang.dto
|
|
||||||
|
|
||||||
data class AngConfig(
|
|
||||||
var index: Int,
|
|
||||||
var vmess: ArrayList<VmessBean>,
|
|
||||||
var subItem: ArrayList<SubItemBean>
|
|
||||||
) {
|
|
||||||
data class VmessBean(var guid: String = "123456",
|
|
||||||
var address: String = "v2ray.cool",
|
|
||||||
var port: Int = 10086,
|
|
||||||
var id: String = "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
|
||||||
var alterId: Int = 64,
|
|
||||||
var security: String = "aes-128-cfb",
|
|
||||||
var network: String = "tcp",
|
|
||||||
var remarks: String = "def",
|
|
||||||
var headerType: String = "",
|
|
||||||
var requestHost: String = "",
|
|
||||||
var path: String = "",
|
|
||||||
var streamSecurity: String = "",
|
|
||||||
var allowInsecure: String = "",
|
|
||||||
var configType: Int = 1,
|
|
||||||
var configVersion: Int = 1,
|
|
||||||
var testResult: String = "",
|
|
||||||
var subid: String = "",
|
|
||||||
var flow: String = "",
|
|
||||||
var sni: String = "")
|
|
||||||
|
|
||||||
data class SubItemBean(var id: String = "",
|
|
||||||
var remarks: String = "",
|
|
||||||
var url: String = "",
|
|
||||||
var enabled: Boolean = true)
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,10 @@ package com.v2ray.ang.dto
|
|||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
|
||||||
data class AppInfo(val appName: String,
|
data class AppInfo(
|
||||||
|
val appName: String,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val appIcon: Drawable,
|
val appIcon: Drawable,
|
||||||
val isSystemApp: Boolean,
|
val isSystemApp: Boolean,
|
||||||
var isSelected: Int)
|
var isSelected: Int
|
||||||
|
)
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
|
||||||
|
|
||||||
enum class EConfigType(val value: Int, val protocolScheme: String) {
|
enum class EConfigType(val value: Int, val protocolScheme: String) {
|
||||||
VMESS(1, "vmess://"),
|
VMESS(1, AppConfig.VMESS),
|
||||||
CUSTOM(2, ""),
|
CUSTOM(2, AppConfig.CUSTOM),
|
||||||
SHADOWSOCKS(3, "ss://"),
|
SHADOWSOCKS(3, AppConfig.SHADOWSOCKS),
|
||||||
SOCKS(4, "socks://"),
|
SOCKS(4, AppConfig.SOCKS),
|
||||||
VLESS(5, "vless://"),
|
VLESS(5, AppConfig.VLESS),
|
||||||
TROJAN(6, "trojan://"),
|
TROJAN(6, AppConfig.TROJAN),
|
||||||
WIREGUARD(7, "wireguard://");
|
WIREGUARD(7, AppConfig.WIREGUARD);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
enum class ERoutingMode(val value: String ) {
|
enum class ERoutingMode(val value: String) {
|
||||||
GLOBAL_PROXY("0"),
|
GLOBAL_PROXY("0"),
|
||||||
BYPASS_LAN("1"),
|
BYPASS_LAN("1"),
|
||||||
BYPASS_MAINLAND("2"),
|
BYPASS_MAINLAND("2"),
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class ProfileItem(
|
||||||
|
val configType: EConfigType,
|
||||||
|
var subscriptionId: String = "",
|
||||||
|
var remarks: String = "",
|
||||||
|
var server: String?,
|
||||||
|
var serverPort: Int?,
|
||||||
|
)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
|
||||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
data class ServerConfig(
|
data class ServerConfig(
|
||||||
@@ -16,26 +16,38 @@ data class ServerConfig(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(configType: EConfigType): ServerConfig {
|
fun create(configType: EConfigType): ServerConfig {
|
||||||
when(configType) {
|
when (configType) {
|
||||||
EConfigType.VMESS, EConfigType.VLESS ->
|
EConfigType.VMESS, EConfigType.VLESS ->
|
||||||
return ServerConfig(
|
return ServerConfig(
|
||||||
configType = configType,
|
configType = configType,
|
||||||
outboundBean = V2rayConfig.OutboundBean(
|
outboundBean = V2rayConfig.OutboundBean(
|
||||||
protocol = configType.name.lowercase(),
|
protocol = configType.name.lowercase(),
|
||||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||||
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
vnext = listOf(
|
||||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
EConfigType.CUSTOM ->
|
EConfigType.CUSTOM ->
|
||||||
return ServerConfig(configType = configType)
|
return ServerConfig(configType = configType)
|
||||||
|
|
||||||
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||||
return ServerConfig(
|
return ServerConfig(
|
||||||
configType = configType,
|
configType = configType,
|
||||||
outboundBean = V2rayConfig.OutboundBean(
|
outboundBean = V2rayConfig.OutboundBean(
|
||||||
protocol = configType.name.lowercase(),
|
protocol = configType.name.lowercase(),
|
||||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())
|
||||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
),
|
||||||
|
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
EConfigType.WIREGUARD ->
|
EConfigType.WIREGUARD ->
|
||||||
return ServerConfig(
|
return ServerConfig(
|
||||||
configType = configType,
|
configType = configType,
|
||||||
@@ -44,7 +56,9 @@ data class ServerConfig(
|
|||||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||||
secretKey = "",
|
secretKey = "",
|
||||||
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
|
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
|
||||||
)))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
data class ServersCache(val guid: String,
|
data class ServersCache(
|
||||||
val config: ServerConfig)
|
val guid: String,
|
||||||
|
val profile: ProfileItem
|
||||||
|
)
|
||||||
@@ -24,7 +24,8 @@ data class V2rayConfig(
|
|||||||
var fakedns: Any? = null,
|
var fakedns: Any? = null,
|
||||||
val browserForwarder: Any? = null,
|
val browserForwarder: Any? = null,
|
||||||
var observatory: Any? = null,
|
var observatory: Any? = null,
|
||||||
var burstObservatory: Any? = null) {
|
var burstObservatory: Any? = null
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_PORT = 443
|
const val DEFAULT_PORT = 443
|
||||||
const val DEFAULT_SECURITY = "auto"
|
const val DEFAULT_SECURITY = "auto"
|
||||||
@@ -36,10 +37,12 @@ data class V2rayConfig(
|
|||||||
const val HTTP = "http"
|
const val HTTP = "http"
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LogBean(val access: String,
|
data class LogBean(
|
||||||
|
val access: String,
|
||||||
val error: String,
|
val error: String,
|
||||||
var loglevel: String?,
|
var loglevel: String?,
|
||||||
val dnsLog: Boolean? = null)
|
val dnsLog: Boolean? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class InboundBean(
|
data class InboundBean(
|
||||||
var tag: String,
|
var tag: String,
|
||||||
@@ -49,30 +52,40 @@ data class V2rayConfig(
|
|||||||
val settings: Any? = null,
|
val settings: Any? = null,
|
||||||
val sniffing: SniffingBean?,
|
val sniffing: SniffingBean?,
|
||||||
val streamSettings: Any? = null,
|
val streamSettings: Any? = null,
|
||||||
val allocate: Any? = null) {
|
val allocate: Any? = null
|
||||||
|
) {
|
||||||
|
|
||||||
data class InSettingsBean(val auth: String? = null,
|
data class InSettingsBean(
|
||||||
|
val auth: String? = null,
|
||||||
val udp: Boolean? = null,
|
val udp: Boolean? = null,
|
||||||
val userLevel: Int? = null,
|
val userLevel: Int? = null,
|
||||||
val address: String? = null,
|
val address: String? = null,
|
||||||
val port: Int? = null,
|
val port: Int? = null,
|
||||||
val network: String? = null)
|
val network: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class SniffingBean(var enabled: Boolean,
|
data class SniffingBean(
|
||||||
|
var enabled: Boolean,
|
||||||
val destOverride: ArrayList<String>,
|
val destOverride: ArrayList<String>,
|
||||||
val metadataOnly: Boolean? = null)
|
val metadataOnly: Boolean? = null,
|
||||||
|
var routeOnly: Boolean? = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class OutboundBean(var tag: String = "proxy",
|
data class OutboundBean(
|
||||||
|
var tag: String = "proxy",
|
||||||
var protocol: String,
|
var protocol: String,
|
||||||
var settings: OutSettingsBean? = null,
|
var settings: OutSettingsBean? = null,
|
||||||
var streamSettings: StreamSettingsBean? = null,
|
var streamSettings: StreamSettingsBean? = null,
|
||||||
val proxySettings: Any? = null,
|
val proxySettings: Any? = null,
|
||||||
val sendThrough: String? = null,
|
val sendThrough: String? = null,
|
||||||
var mux: MuxBean? = MuxBean(false)) {
|
var mux: MuxBean? = MuxBean(false)
|
||||||
|
) {
|
||||||
|
|
||||||
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
|
data class OutSettingsBean(
|
||||||
|
var vnext: List<VnextBean>? = null,
|
||||||
var fragment: FragmentBean? = null,
|
var fragment: FragmentBean? = null,
|
||||||
|
var noise: NoiseBean? = null,
|
||||||
var servers: List<ServersBean>? = null,
|
var servers: List<ServersBean>? = null,
|
||||||
/*Blackhole*/
|
/*Blackhole*/
|
||||||
var response: Response? = null,
|
var response: Response? = null,
|
||||||
@@ -90,26 +103,38 @@ data class V2rayConfig(
|
|||||||
var secretKey: String? = null,
|
var secretKey: String? = null,
|
||||||
val peers: List<WireGuardBean>? = null,
|
val peers: List<WireGuardBean>? = null,
|
||||||
var reserved: List<Int>? = null,
|
var reserved: List<Int>? = null,
|
||||||
var mtu :Int? = null
|
var mtu: Int? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class VnextBean(var address: String = "",
|
data class VnextBean(
|
||||||
|
var address: String = "",
|
||||||
var port: Int = DEFAULT_PORT,
|
var port: Int = DEFAULT_PORT,
|
||||||
var users: List<UsersBean>) {
|
var users: List<UsersBean>
|
||||||
|
) {
|
||||||
|
|
||||||
data class UsersBean(var id: String = "",
|
data class UsersBean(
|
||||||
|
var id: String = "",
|
||||||
var alterId: Int? = null,
|
var alterId: Int? = null,
|
||||||
var security: String = DEFAULT_SECURITY,
|
var security: String = DEFAULT_SECURITY,
|
||||||
var level: Int = DEFAULT_LEVEL,
|
var level: Int = DEFAULT_LEVEL,
|
||||||
var encryption: String = "",
|
var encryption: String = "",
|
||||||
var flow: String = "")
|
var flow: String = ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FragmentBean(var packets: String? = null,
|
data class FragmentBean(
|
||||||
|
var packets: String? = null,
|
||||||
var length: String? = null,
|
var length: String? = null,
|
||||||
var interval: String? = null)
|
var interval: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class ServersBean(var address: String = "",
|
data class NoiseBean(
|
||||||
|
var packet: String? = null,
|
||||||
|
var delay: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ServersBean(
|
||||||
|
var address: String = "",
|
||||||
var method: String = "chacha20-poly1305",
|
var method: String = "chacha20-poly1305",
|
||||||
var ota: Boolean = false,
|
var ota: Boolean = false,
|
||||||
var password: String = "",
|
var password: String = "",
|
||||||
@@ -118,26 +143,33 @@ data class V2rayConfig(
|
|||||||
val email: String? = null,
|
val email: String? = null,
|
||||||
var flow: String? = null,
|
var flow: String? = null,
|
||||||
val ivCheck: Boolean? = null,
|
val ivCheck: Boolean? = null,
|
||||||
var users: List<SocksUsersBean>? = null) {
|
var users: List<SocksUsersBean>? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
data class SocksUsersBean(var user: String = "",
|
data class SocksUsersBean(
|
||||||
|
var user: String = "",
|
||||||
var pass: String = "",
|
var pass: String = "",
|
||||||
var level: Int = DEFAULT_LEVEL)
|
var level: Int = DEFAULT_LEVEL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Response(var type: String)
|
data class Response(var type: String)
|
||||||
|
|
||||||
data class WireGuardBean(var publicKey: String = "",
|
data class WireGuardBean(
|
||||||
var endpoint: String = "")
|
var publicKey: String = "",
|
||||||
|
var endpoint: String = ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
|
data class StreamSettingsBean(
|
||||||
|
var network: String = DEFAULT_NETWORK,
|
||||||
var security: String = "",
|
var security: String = "",
|
||||||
var tcpSettings: TcpSettingsBean? = null,
|
var tcpSettings: TcpSettingsBean? = null,
|
||||||
var kcpSettings: KcpSettingsBean? = null,
|
var kcpSettings: KcpSettingsBean? = null,
|
||||||
var wsSettings: WsSettingsBean? = null,
|
var wsSettings: WsSettingsBean? = null,
|
||||||
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
||||||
|
var splithttpSettings: SplithttpSettingsBean? = null,
|
||||||
var httpSettings: HttpSettingsBean? = null,
|
var httpSettings: HttpSettingsBean? = null,
|
||||||
var tlsSettings: TlsSettingsBean? = null,
|
var tlsSettings: TlsSettingsBean? = null,
|
||||||
var quicSettings: QuicSettingBean? = null,
|
var quicSettings: QuicSettingBean? = null,
|
||||||
@@ -147,27 +179,36 @@ data class V2rayConfig(
|
|||||||
var sockopt: SockoptBean? = null
|
var sockopt: SockoptBean? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
|
data class TcpSettingsBean(
|
||||||
val acceptProxyProtocol: Boolean? = null) {
|
var header: HeaderBean = HeaderBean(),
|
||||||
data class HeaderBean(var type: String = "none",
|
val acceptProxyProtocol: Boolean? = null
|
||||||
|
) {
|
||||||
|
data class HeaderBean(
|
||||||
|
var type: String = "none",
|
||||||
var request: RequestBean? = null,
|
var request: RequestBean? = null,
|
||||||
var response: Any? = null) {
|
var response: Any? = null
|
||||||
data class RequestBean(var path: List<String> = ArrayList(),
|
) {
|
||||||
|
data class RequestBean(
|
||||||
|
var path: List<String> = ArrayList(),
|
||||||
var headers: HeadersBean = HeadersBean(),
|
var headers: HeadersBean = HeadersBean(),
|
||||||
val version: String? = null,
|
val version: String? = null,
|
||||||
val method: String? = null) {
|
val method: String? = null
|
||||||
data class HeadersBean(var Host: List<String> = ArrayList(),
|
) {
|
||||||
|
data class HeadersBean(
|
||||||
|
var Host: List<String>? = ArrayList(),
|
||||||
@SerializedName("User-Agent")
|
@SerializedName("User-Agent")
|
||||||
val userAgent: List<String>? = null,
|
val userAgent: List<String>? = null,
|
||||||
@SerializedName("Accept-Encoding")
|
@SerializedName("Accept-Encoding")
|
||||||
val acceptEncoding: List<String>? = null,
|
val acceptEncoding: List<String>? = null,
|
||||||
val Connection: List<String>? = null,
|
val Connection: List<String>? = null,
|
||||||
val Pragma: String? = null)
|
val Pragma: String? = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class KcpSettingsBean(var mtu: Int = 1350,
|
data class KcpSettingsBean(
|
||||||
|
var mtu: Int = 1350,
|
||||||
var tti: Int = 50,
|
var tti: Int = 50,
|
||||||
var uplinkCapacity: Int = 12,
|
var uplinkCapacity: Int = 12,
|
||||||
var downlinkCapacity: Int = 100,
|
var downlinkCapacity: Int = 100,
|
||||||
@@ -175,33 +216,50 @@ data class V2rayConfig(
|
|||||||
var readBufferSize: Int = 1,
|
var readBufferSize: Int = 1,
|
||||||
var writeBufferSize: Int = 1,
|
var writeBufferSize: Int = 1,
|
||||||
var header: HeaderBean = HeaderBean(),
|
var header: HeaderBean = HeaderBean(),
|
||||||
var seed: String? = null) {
|
var seed: String? = null
|
||||||
|
) {
|
||||||
data class HeaderBean(var type: String = "none")
|
data class HeaderBean(var type: String = "none")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WsSettingsBean(var path: String = "",
|
data class WsSettingsBean(
|
||||||
|
var path: String = "",
|
||||||
var headers: HeadersBean = HeadersBean(),
|
var headers: HeadersBean = HeadersBean(),
|
||||||
val maxEarlyData: Int? = null,
|
val maxEarlyData: Int? = null,
|
||||||
val useBrowserForwarding: Boolean? = null,
|
val useBrowserForwarding: Boolean? = null,
|
||||||
val acceptProxyProtocol: Boolean? = null) {
|
val acceptProxyProtocol: Boolean? = null
|
||||||
|
) {
|
||||||
data class HeadersBean(var Host: String = "")
|
data class HeadersBean(var Host: String = "")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class HttpupgradeSettingsBean(var path: String = "",
|
data class HttpupgradeSettingsBean(
|
||||||
|
var path: String = "",
|
||||||
var host: String = "",
|
var host: String = "",
|
||||||
val acceptProxyProtocol: Boolean? = null)
|
val acceptProxyProtocol: Boolean? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
data class SplithttpSettingsBean(
|
||||||
var path: String = "")
|
var path: String = "",
|
||||||
|
var host: String = "",
|
||||||
|
val maxUploadSize: Int? = null,
|
||||||
|
val maxConcurrentUploads: Int? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class SockoptBean(var TcpNoDelay: Boolean? = null,
|
data class HttpSettingsBean(
|
||||||
|
var host: List<String> = ArrayList(),
|
||||||
|
var path: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SockoptBean(
|
||||||
|
var TcpNoDelay: Boolean? = null,
|
||||||
var tcpKeepAliveIdle: Int? = null,
|
var tcpKeepAliveIdle: Int? = null,
|
||||||
var tcpFastOpen: Boolean? = null,
|
var tcpFastOpen: Boolean? = null,
|
||||||
var tproxy: String? = null,
|
var tproxy: String? = null,
|
||||||
var mark: Int? = null,
|
var mark: Int? = null,
|
||||||
var dialerProxy: String? = null)
|
var dialerProxy: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class TlsSettingsBean(var allowInsecure: Boolean = false,
|
data class TlsSettingsBean(
|
||||||
|
var allowInsecure: Boolean = false,
|
||||||
var serverName: String = "",
|
var serverName: String = "",
|
||||||
val alpn: List<String>? = null,
|
val alpn: List<String>? = null,
|
||||||
val minVersion: String? = null,
|
val minVersion: String? = null,
|
||||||
@@ -216,21 +274,30 @@ data class V2rayConfig(
|
|||||||
val show: Boolean = false,
|
val show: Boolean = false,
|
||||||
var publicKey: String? = null,
|
var publicKey: String? = null,
|
||||||
var shortId: String? = null,
|
var shortId: String? = null,
|
||||||
var spiderX: String? = null)
|
var spiderX: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class QuicSettingBean(var security: String = "none",
|
data class QuicSettingBean(
|
||||||
|
var security: String = "none",
|
||||||
var key: String = "",
|
var key: String = "",
|
||||||
var header: HeaderBean = HeaderBean()) {
|
var header: HeaderBean = HeaderBean()
|
||||||
|
) {
|
||||||
data class HeaderBean(var type: String = "none")
|
data class HeaderBean(var type: String = "none")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GrpcSettingsBean(var serviceName: String = "",
|
data class GrpcSettingsBean(
|
||||||
|
var serviceName: String = "",
|
||||||
var authority: String? = null,
|
var authority: String? = null,
|
||||||
var multiMode: Boolean? = null)
|
var multiMode: Boolean? = null,
|
||||||
|
var idle_timeout: Int? = null,
|
||||||
|
var health_check_timeout: Int? = null
|
||||||
|
)
|
||||||
|
|
||||||
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
fun populateTransportSettings(
|
||||||
|
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
||||||
authority: String?): String {
|
authority: String?
|
||||||
|
): String {
|
||||||
var sni = ""
|
var sni = ""
|
||||||
network = transport
|
network = transport
|
||||||
when (network) {
|
when (network) {
|
||||||
@@ -240,17 +307,18 @@ data class V2rayConfig(
|
|||||||
tcpSetting.header.type = HTTP
|
tcpSetting.header.type = HTTP
|
||||||
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
||||||
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
||||||
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.headers.Host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.path = (path.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
tcpSetting.header.request = requestObj
|
tcpSetting.header.request = requestObj
|
||||||
sni = requestObj.headers.Host.getOrNull(0) ?: sni
|
sni = requestObj.headers.Host?.getOrNull(0) ?: sni
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tcpSetting.header.type = "none"
|
tcpSetting.header.type = "none"
|
||||||
sni = host ?: ""
|
sni = host.orEmpty()
|
||||||
}
|
}
|
||||||
tcpSettings = tcpSetting
|
tcpSettings = tcpSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp" -> {
|
"kcp" -> {
|
||||||
val kcpsetting = KcpSettingsBean()
|
val kcpsetting = KcpSettingsBean()
|
||||||
kcpsetting.header.type = headerType ?: "none"
|
kcpsetting.header.type = headerType ?: "none"
|
||||||
@@ -261,49 +329,66 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
kcpSettings = kcpsetting
|
kcpSettings = kcpsetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws" -> {
|
"ws" -> {
|
||||||
val wssetting = WsSettingsBean()
|
val wssetting = WsSettingsBean()
|
||||||
wssetting.headers.Host = host ?: ""
|
wssetting.headers.Host = host.orEmpty()
|
||||||
sni = wssetting.headers.Host
|
sni = wssetting.headers.Host
|
||||||
wssetting.path = path ?: "/"
|
wssetting.path = path ?: "/"
|
||||||
wsSettings = wssetting
|
wsSettings = wssetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"httpupgrade" -> {
|
"httpupgrade" -> {
|
||||||
val httpupgradeSetting = HttpupgradeSettingsBean()
|
val httpupgradeSetting = HttpupgradeSettingsBean()
|
||||||
httpupgradeSetting.host = host ?: ""
|
httpupgradeSetting.host = host.orEmpty()
|
||||||
sni = httpupgradeSetting.host
|
sni = httpupgradeSetting.host
|
||||||
httpupgradeSetting.path = path ?: "/"
|
httpupgradeSetting.path = path ?: "/"
|
||||||
httpupgradeSettings = httpupgradeSetting
|
httpupgradeSettings = httpupgradeSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"splithttp" -> {
|
||||||
|
val splithttpSetting = SplithttpSettingsBean()
|
||||||
|
splithttpSetting.host = host.orEmpty()
|
||||||
|
sni = splithttpSetting.host
|
||||||
|
splithttpSetting.path = path ?: "/"
|
||||||
|
splithttpSettings = splithttpSetting
|
||||||
|
}
|
||||||
|
|
||||||
"h2", "http" -> {
|
"h2", "http" -> {
|
||||||
network = "h2"
|
network = "h2"
|
||||||
val h2Setting = HttpSettingsBean()
|
val h2Setting = HttpSettingsBean()
|
||||||
h2Setting.host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
h2Setting.host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
sni = h2Setting.host.getOrNull(0) ?: sni
|
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||||
h2Setting.path = path ?: "/"
|
h2Setting.path = path ?: "/"
|
||||||
httpSettings = h2Setting
|
httpSettings = h2Setting
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
"quic" -> {
|
||||||
val quicsetting = QuicSettingBean()
|
val quicsetting = QuicSettingBean()
|
||||||
quicsetting.security = quicSecurity ?: "none"
|
quicsetting.security = quicSecurity ?: "none"
|
||||||
quicsetting.key = key ?: ""
|
quicsetting.key = key.orEmpty()
|
||||||
quicsetting.header.type = headerType ?: "none"
|
quicsetting.header.type = headerType ?: "none"
|
||||||
quicSettings = quicsetting
|
quicSettings = quicsetting
|
||||||
}
|
}
|
||||||
|
|
||||||
"grpc" -> {
|
"grpc" -> {
|
||||||
val grpcSetting = GrpcSettingsBean()
|
val grpcSetting = GrpcSettingsBean()
|
||||||
grpcSetting.multiMode = mode == "multi"
|
grpcSetting.multiMode = mode == "multi"
|
||||||
grpcSetting.serviceName = serviceName ?: ""
|
grpcSetting.serviceName = serviceName.orEmpty()
|
||||||
grpcSetting.authority = authority ?: ""
|
grpcSetting.authority = authority.orEmpty()
|
||||||
sni = authority ?: ""
|
grpcSetting.idle_timeout = 60
|
||||||
|
grpcSetting.health_check_timeout = 20
|
||||||
|
sni = authority.orEmpty()
|
||||||
grpcSettings = grpcSetting
|
grpcSettings = grpcSetting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sni
|
return sni
|
||||||
}
|
}
|
||||||
|
|
||||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
|
fun populateTlsSettings(
|
||||||
publicKey: String?, shortId: String?, spiderX: String?) {
|
streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
|
||||||
|
publicKey: String?, shortId: String?, spiderX: String?
|
||||||
|
) {
|
||||||
security = streamSecurity
|
security = streamSecurity
|
||||||
val tlsSetting = TlsSettingsBean(
|
val tlsSetting = TlsSettingsBean(
|
||||||
allowInsecure = allowInsecure,
|
allowInsecure = allowInsecure,
|
||||||
@@ -324,18 +409,22 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MuxBean(var enabled: Boolean,
|
data class MuxBean(
|
||||||
|
var enabled: Boolean,
|
||||||
var concurrency: Int = 8,
|
var concurrency: Int = 8,
|
||||||
var xudpConcurrency: Int = 8,
|
var xudpConcurrency: Int = 8,
|
||||||
var xudpProxyUDP443: String = "",)
|
var xudpProxyUDP443: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
fun getServerAddress(): String? {
|
fun getServerAddress(): String? {
|
||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
|
) {
|
||||||
return settings?.vnext?.get(0)?.address
|
return settings?.vnext?.get(0)?.address
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|
) {
|
||||||
return settings?.servers?.get(0)?.address
|
return settings?.servers?.get(0)?.address
|
||||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||||
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
|
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
|
||||||
@@ -345,11 +434,13 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
fun getServerPort(): Int? {
|
fun getServerPort(): Int? {
|
||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
|
) {
|
||||||
return settings?.vnext?.get(0)?.port
|
return settings?.vnext?.get(0)?.port
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|
) {
|
||||||
return settings?.servers?.get(0)?.port
|
return settings?.servers?.get(0)?.port
|
||||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||||
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
|
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
|
||||||
@@ -359,10 +450,12 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
fun getPassword(): String? {
|
fun getPassword(): String? {
|
||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
|
) {
|
||||||
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
||||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|
) {
|
||||||
return settings?.servers?.get(0)?.password
|
return settings?.servers?.get(0)?.password
|
||||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
|
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
|
||||||
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
||||||
@@ -385,51 +478,82 @@ data class V2rayConfig(
|
|||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||||
|| protocol.equals(EConfigType.SHADOWSOCKS.name, true)) {
|
|| protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||||
|
) {
|
||||||
val transport = streamSettings?.network ?: return null
|
val transport = streamSettings?.network ?: return null
|
||||||
return when (transport) {
|
return when (transport) {
|
||||||
"tcp" -> {
|
"tcp" -> {
|
||||||
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
||||||
listOf(tcpSetting.header.type,
|
listOf(
|
||||||
|
tcpSetting.header.type,
|
||||||
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
|
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
|
||||||
tcpSetting.header.request?.path?.joinToString().orEmpty())
|
tcpSetting.header.request?.path?.joinToString().orEmpty()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"kcp" -> {
|
"kcp" -> {
|
||||||
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
||||||
listOf(kcpSetting.header.type,
|
listOf(
|
||||||
|
kcpSetting.header.type,
|
||||||
"",
|
"",
|
||||||
kcpSetting.seed.orEmpty())
|
kcpSetting.seed.orEmpty()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"ws" -> {
|
"ws" -> {
|
||||||
val wsSetting = streamSettings?.wsSettings ?: return null
|
val wsSetting = streamSettings?.wsSettings ?: return null
|
||||||
listOf("",
|
listOf(
|
||||||
|
"",
|
||||||
wsSetting.headers.Host,
|
wsSetting.headers.Host,
|
||||||
wsSetting.path)
|
wsSetting.path
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"httpupgrade" -> {
|
"httpupgrade" -> {
|
||||||
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
|
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
|
||||||
listOf("",
|
listOf(
|
||||||
|
"",
|
||||||
httpupgradeSetting.host,
|
httpupgradeSetting.host,
|
||||||
httpupgradeSetting.path)
|
httpupgradeSetting.path
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"splithttp" -> {
|
||||||
|
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
|
||||||
|
listOf(
|
||||||
|
"",
|
||||||
|
splithttpSetting.host,
|
||||||
|
splithttpSetting.path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
"h2" -> {
|
"h2" -> {
|
||||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||||
listOf("",
|
listOf(
|
||||||
|
"",
|
||||||
h2Setting.host.joinToString(),
|
h2Setting.host.joinToString(),
|
||||||
h2Setting.path)
|
h2Setting.path
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"quic" -> {
|
"quic" -> {
|
||||||
val quicSetting = streamSettings?.quicSettings ?: return null
|
val quicSetting = streamSettings?.quicSettings ?: return null
|
||||||
listOf(quicSetting.header.type,
|
listOf(
|
||||||
|
quicSetting.header.type,
|
||||||
quicSetting.security,
|
quicSetting.security,
|
||||||
quicSetting.key)
|
quicSetting.key
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"grpc" -> {
|
"grpc" -> {
|
||||||
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
||||||
listOf(if (grpcSetting.multiMode == true) "multi" else "gun",
|
listOf(
|
||||||
grpcSetting.authority ?: "",
|
if (grpcSetting.multiMode == true) "multi" else "gun",
|
||||||
grpcSetting.serviceName)
|
grpcSetting.authority.orEmpty(),
|
||||||
|
grpcSetting.serviceName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,24 +561,29 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DnsBean(var servers: ArrayList<Any>? = null,
|
data class DnsBean(
|
||||||
|
var servers: ArrayList<Any>? = null,
|
||||||
var hosts: Map<String, Any>? = null,
|
var hosts: Map<String, Any>? = null,
|
||||||
val clientIp: String? = null,
|
val clientIp: String? = null,
|
||||||
val disableCache: Boolean? = null,
|
val disableCache: Boolean? = null,
|
||||||
val queryStrategy: String? = null,
|
val queryStrategy: String? = null,
|
||||||
val tag: String? = null
|
val tag: String? = null
|
||||||
) {
|
) {
|
||||||
data class ServersBean(var address: String = "",
|
data class ServersBean(
|
||||||
|
var address: String = "",
|
||||||
var port: Int? = null,
|
var port: Int? = null,
|
||||||
var domains: List<String>? = null,
|
var domains: List<String>? = null,
|
||||||
var expectIPs: List<String>? = null,
|
var expectIPs: List<String>? = null,
|
||||||
val clientIp: String? = null)
|
val clientIp: String? = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoutingBean(var domainStrategy: String,
|
data class RoutingBean(
|
||||||
|
var domainStrategy: String,
|
||||||
var domainMatcher: String? = null,
|
var domainMatcher: String? = null,
|
||||||
var rules: ArrayList<RulesBean>,
|
var rules: ArrayList<RulesBean>,
|
||||||
val balancers: List<Any>? = null) {
|
val balancers: List<Any>? = null
|
||||||
|
) {
|
||||||
|
|
||||||
data class RulesBean(
|
data class RulesBean(
|
||||||
var ip: ArrayList<String>? = null,
|
var ip: ArrayList<String>? = null,
|
||||||
@@ -473,8 +602,10 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PolicyBean(var levels: Map<String, LevelBean>,
|
data class PolicyBean(
|
||||||
var system: Any? = null) {
|
var levels: Map<String, LevelBean>,
|
||||||
|
var system: Any? = null
|
||||||
|
) {
|
||||||
data class LevelBean(
|
data class LevelBean(
|
||||||
var handshake: Int? = null,
|
var handshake: Int? = null,
|
||||||
var connIdle: Int? = null,
|
var connIdle: Int? = null,
|
||||||
@@ -482,11 +613,14 @@ data class V2rayConfig(
|
|||||||
var downlinkOnly: Int? = null,
|
var downlinkOnly: Int? = null,
|
||||||
val statsUserUplink: Boolean? = null,
|
val statsUserUplink: Boolean? = null,
|
||||||
val statsUserDownlink: Boolean? = null,
|
val statsUserDownlink: Boolean? = null,
|
||||||
var bufferSize: Int? = null)
|
var bufferSize: Int? = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FakednsBean(var ipPool: String = "198.18.0.0/15",
|
data class FakednsBean(
|
||||||
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
var ipPool: String = "198.18.0.0/15",
|
||||||
|
var poolSize: Int = 10000
|
||||||
|
) // roughly 10 times smaller than total ip pool
|
||||||
|
|
||||||
fun getProxyOutbound(): OutboundBean? {
|
fun getProxyOutbound(): OutboundBean? {
|
||||||
outbounds.forEach { outbound ->
|
outbounds.forEach { outbound ->
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
data class VmessQRCode(var v: String = "",
|
data class VmessQRCode(
|
||||||
|
var v: String = "",
|
||||||
var ps: String = "",
|
var ps: String = "",
|
||||||
var add: String = "",
|
var add: String = "",
|
||||||
var port: String = "",
|
var port: String = "",
|
||||||
@@ -14,4 +15,5 @@ data class VmessQRCode(var v: String = "",
|
|||||||
var tls: String = "",
|
var tls: String = "",
|
||||||
var sni: String = "",
|
var sni: String = "",
|
||||||
var alpn: String = "",
|
var alpn: String = "",
|
||||||
var fp: String = "")
|
var fp: String = ""
|
||||||
|
)
|
||||||
@@ -9,15 +9,15 @@ import org.json.JSONObject
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
|
||||||
val Context.v2RayApplication: AngApplication
|
val Context.v2RayApplication: AngApplication?
|
||||||
get() = applicationContext as AngApplication
|
get() = applicationContext as? AngApplication
|
||||||
|
|
||||||
fun Context.toast(message: Int) {
|
fun Context.toast(message: Int) {
|
||||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
|
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.toast(message: CharSequence) {
|
fun Context.toast(message: CharSequence) {
|
||||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
|
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JSONObject.putOpt(pair: Pair<String, Any?>) {
|
fun JSONObject.putOpt(pair: Pair<String, Any?>) {
|
||||||
@@ -34,26 +34,14 @@ const val DIVISOR = 1024.0
|
|||||||
fun Long.toSpeedString(): String = this.toTrafficString() + "/s"
|
fun Long.toSpeedString(): String = this.toTrafficString() + "/s"
|
||||||
|
|
||||||
fun Long.toTrafficString(): String {
|
fun Long.toTrafficString(): String {
|
||||||
if (this < THRESHOLD) {
|
val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB")
|
||||||
return "$this B"
|
var size = this.toDouble()
|
||||||
|
var unitIndex = 0
|
||||||
|
while (size >= THRESHOLD && unitIndex < units.size - 1) {
|
||||||
|
size /= DIVISOR
|
||||||
|
unitIndex++
|
||||||
}
|
}
|
||||||
val kb = this / DIVISOR
|
return String.format("%.1f %s", size, units[unitIndex])
|
||||||
if (kb < THRESHOLD) {
|
|
||||||
return "${String.format("%.1f KB", kb)}"
|
|
||||||
}
|
|
||||||
val mb = kb / DIVISOR
|
|
||||||
if (mb < THRESHOLD) {
|
|
||||||
return "${String.format("%.1f MB", mb)}"
|
|
||||||
}
|
|
||||||
val gb = mb / DIVISOR
|
|
||||||
if (gb < THRESHOLD) {
|
|
||||||
return "${String.format("%.1f GB", gb)}"
|
|
||||||
}
|
|
||||||
val tb = gb / DIVISOR
|
|
||||||
if (tb < THRESHOLD) {
|
|
||||||
return "${String.format("%.1f TB", tb)}"
|
|
||||||
}
|
|
||||||
return String.format("%.1f PB", tb / DIVISOR)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val URLConnection.responseLength: Long
|
val URLConnection.responseLength: Long
|
||||||
@@ -64,6 +52,6 @@ val URLConnection.responseLength: Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
val URI.idnHost: String
|
val URI.idnHost: String
|
||||||
get() = host?.replace("[", "")?.replace("]", "") ?: ""
|
get() = host?.replace("[", "")?.replace("]", "").orEmpty()
|
||||||
|
|
||||||
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
|
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
|
||||||
@@ -35,7 +35,8 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
} else {
|
} else {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
})
|
}
|
||||||
|
)
|
||||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stop_24dp)
|
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stop_24dp)
|
||||||
@@ -65,12 +66,17 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
AppWidgetManager.getInstance(context)?.let { manager ->
|
AppWidgetManager.getInstance(context)?.let { manager ->
|
||||||
when (intent.getIntExtra("key", 0)) {
|
when (intent.getIntExtra("key", 0)) {
|
||||||
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
|
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
updateWidgetBackground(
|
||||||
true)
|
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
updateWidgetBackground(
|
||||||
false)
|
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class QSTileService : TileService() {
|
|||||||
Tile.STATE_INACTIVE -> {
|
Tile.STATE_INACTIVE -> {
|
||||||
Utils.startVServiceFromToggle(this)
|
Utils.startVServiceFromToggle(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tile.STATE_ACTIVE -> {
|
Tile.STATE_ACTIVE -> {
|
||||||
Utils.stopVService(this)
|
Utils.stopVService(this)
|
||||||
}
|
}
|
||||||
@@ -67,22 +68,26 @@ class QSTileService : TileService() {
|
|||||||
private var mMsgReceive: BroadcastReceiver? = null
|
private var mMsgReceive: BroadcastReceiver? = null
|
||||||
|
|
||||||
private class ReceiveMessageHandler(context: QSTileService) : BroadcastReceiver() {
|
private class ReceiveMessageHandler(context: QSTileService) : BroadcastReceiver() {
|
||||||
internal var mReference: SoftReference<QSTileService> = SoftReference(context)
|
var mReference: SoftReference<QSTileService> = SoftReference(context)
|
||||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||||
val context = mReference.get()
|
val context = mReference.get()
|
||||||
when (intent?.getIntExtra("key", 0)) {
|
when (intent?.getIntExtra("key", 0)) {
|
||||||
AppConfig.MSG_STATE_RUNNING -> {
|
AppConfig.MSG_STATE_RUNNING -> {
|
||||||
context?.setState(Tile.STATE_ACTIVE)
|
context?.setState(Tile.STATE_ACTIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_NOT_RUNNING -> {
|
AppConfig.MSG_STATE_NOT_RUNNING -> {
|
||||||
context?.setState(Tile.STATE_INACTIVE)
|
context?.setState(Tile.STATE_INACTIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_START_SUCCESS -> {
|
AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||||
context?.setState(Tile.STATE_ACTIVE)
|
context?.setState(Tile.STATE_ACTIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_START_FAILURE -> {
|
AppConfig.MSG_STATE_START_FAILURE -> {
|
||||||
context?.setState(Tile.STATE_INACTIVE)
|
context?.setState(Tile.STATE_INACTIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||||
context?.setState(Tile.STATE_INACTIVE)
|
context?.setState(Tile.STATE_INACTIVE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
|
||||||
|
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
@@ -18,14 +20,13 @@ import com.v2ray.ang.util.Utils
|
|||||||
|
|
||||||
object SubscriptionUpdater {
|
object SubscriptionUpdater {
|
||||||
|
|
||||||
const val notificationChannel = "subscription_update_channel"
|
|
||||||
|
|
||||||
class UpdateTask(context: Context, params: WorkerParameters) :
|
class UpdateTask(context: Context, params: WorkerParameters) :
|
||||||
CoroutineWorker(context, params) {
|
CoroutineWorker(context, params) {
|
||||||
|
|
||||||
private val notificationManager = NotificationManagerCompat.from(applicationContext)
|
private val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
private val notification =
|
private val notification =
|
||||||
NotificationCompat.Builder(applicationContext, notificationChannel)
|
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL)
|
||||||
.setWhen(0)
|
.setWhen(0)
|
||||||
.setTicker("Update")
|
.setTicker("Update")
|
||||||
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
|
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
|
||||||
@@ -43,11 +44,11 @@ object SubscriptionUpdater {
|
|||||||
val subscription = i.second
|
val subscription = i.second
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
notification.setChannelId(notificationChannel)
|
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
|
||||||
val channel =
|
val channel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
notificationChannel,
|
SUBSCRIPTION_UPDATE_CHANNEL,
|
||||||
"Subscription Update Service",
|
SUBSCRIPTION_UPDATE_CHANNEL_NAME,
|
||||||
NotificationManager.IMPORTANCE_MIN
|
NotificationManager.IMPORTANCE_MIN
|
||||||
)
|
)
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
@@ -58,23 +59,11 @@ object SubscriptionUpdater {
|
|||||||
"subscription automatic update: ---${subscription.remarks}"
|
"subscription automatic update: ---${subscription.remarks}"
|
||||||
)
|
)
|
||||||
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
||||||
importBatchConfig(configs, i.first)
|
AngConfigManager.importBatchConfig(configs, i.first, false)
|
||||||
notification.setContentText("Updating ${subscription.remarks}")
|
notification.setContentText("Updating ${subscription.remarks}")
|
||||||
}
|
}
|
||||||
notificationManager.cancel(3)
|
notificationManager.cancel(3)
|
||||||
return Result.success()
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
|||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
val context = newBase?.let {
|
val context = newBase?.let {
|
||||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||||
}
|
}
|
||||||
super.attachBaseContext(context)
|
super.attachBaseContext(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ import com.v2ray.ang.util.MmkvManager
|
|||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.util.V2rayConfigUtil
|
import com.v2ray.ang.util.V2rayConfigUtil
|
||||||
import go.Seq
|
import go.Seq
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import libv2ray.V2RayPoint
|
import libv2ray.V2RayPoint
|
||||||
import libv2ray.V2RayVPNServiceSupportsSet
|
import libv2ray.V2RayVPNServiceSupportsSet
|
||||||
import rx.Observable
|
|
||||||
import rx.Subscription
|
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -59,16 +59,21 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
private var lastQueryTime = 0L
|
private var lastQueryTime = 0L
|
||||||
private var mBuilder: NotificationCompat.Builder? = null
|
private var mBuilder: NotificationCompat.Builder? = null
|
||||||
private var mSubscription: Subscription? = null
|
private var mDisposable: Disposable? = null
|
||||||
private var mNotificationManager: NotificationManager? = null
|
private var mNotificationManager: NotificationManager? = null
|
||||||
|
|
||||||
fun startV2Ray(context: Context) {
|
fun startV2Ray(context: Context) {
|
||||||
|
if (v2rayPoint.isRunning) return
|
||||||
|
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||||
|
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||||
|
if (!result.status) return
|
||||||
|
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
|
||||||
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
||||||
} else {
|
} else {
|
||||||
context.toast(R.string.toast_services_start)
|
context.toast(R.string.toast_services_start)
|
||||||
}
|
}
|
||||||
val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
|
val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||||
Intent(context.applicationContext, V2RayVpnService::class.java)
|
Intent(context.applicationContext, V2RayVpnService::class.java)
|
||||||
} else {
|
} else {
|
||||||
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
|
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
|
||||||
@@ -126,7 +131,9 @@ object V2RayServiceManager {
|
|||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||||
if (!v2rayPoint.isRunning) {
|
if (v2rayPoint.isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
|
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
|
||||||
if (!result.status)
|
if (!result.status)
|
||||||
return
|
return
|
||||||
@@ -163,13 +170,12 @@ object V2RayServiceManager {
|
|||||||
cancelNotification()
|
cancelNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun stopV2rayPoint() {
|
fun stopV2rayPoint() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
|
|
||||||
if (v2rayPoint.isRunning) {
|
if (v2rayPoint.isRunning) {
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
v2rayPoint.stopLoop()
|
v2rayPoint.stopLoop()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -200,18 +206,23 @@ object V2RayServiceManager {
|
|||||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_START -> {
|
AppConfig.MSG_STATE_START -> {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_STOP -> {
|
AppConfig.MSG_STATE_STOP -> {
|
||||||
serviceControl.stopService()
|
serviceControl.stopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_STATE_RESTART -> {
|
AppConfig.MSG_STATE_RESTART -> {
|
||||||
startV2rayPoint()
|
startV2rayPoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_MEASURE_DELAY -> {
|
AppConfig.MSG_MEASURE_DELAY -> {
|
||||||
measureV2rayDelay()
|
measureV2rayDelay()
|
||||||
}
|
}
|
||||||
@@ -222,6 +233,7 @@ object V2RayServiceManager {
|
|||||||
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||||
stopSpeedNotification()
|
stopSpeedNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent.ACTION_SCREEN_ON -> {
|
Intent.ACTION_SCREEN_ON -> {
|
||||||
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||||
startSpeedNotification()
|
startSpeedNotification()
|
||||||
@@ -231,17 +243,25 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun measureV2rayDelay() {
|
private fun measureV2rayDelay() {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val service = serviceControl?.get()?.getService() ?: return@launch
|
val service = serviceControl?.get()?.getService() ?: return@launch
|
||||||
var time = -1L
|
var time = -1L
|
||||||
var errstr = ""
|
var errstr = ""
|
||||||
if (v2rayPoint.isRunning) {
|
if (v2rayPoint.isRunning) {
|
||||||
try {
|
try {
|
||||||
time = v2rayPoint.measureDelay()
|
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
}
|
}
|
||||||
|
if (time == -1L) {
|
||||||
|
try {
|
||||||
|
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl(true))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||||
|
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val result = if (time == -1L) {
|
val result = if (time == -1L) {
|
||||||
service.getString(R.string.connection_test_error, errstr)
|
service.getString(R.string.connection_test_error, errstr)
|
||||||
@@ -256,25 +276,29 @@ object V2RayServiceManager {
|
|||||||
private fun showNotification() {
|
private fun showNotification() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||||
val contentPendingIntent = PendingIntent.getActivity(service,
|
val contentPendingIntent = PendingIntent.getActivity(
|
||||||
|
service,
|
||||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
} else {
|
} else {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
stopV2RayIntent.`package` = ANG_PACKAGE
|
stopV2RayIntent.`package` = ANG_PACKAGE
|
||||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||||
|
|
||||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
val stopV2RayPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
service,
|
||||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
} else {
|
} else {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val channelId =
|
val channelId =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -293,9 +317,11 @@ object V2RayServiceManager {
|
|||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setContentIntent(contentPendingIntent)
|
.setContentIntent(contentPendingIntent)
|
||||||
.addAction(R.drawable.ic_delete_24dp,
|
.addAction(
|
||||||
|
R.drawable.ic_delete_24dp,
|
||||||
service.getString(R.string.notification_action_stop_v2ray),
|
service.getString(R.string.notification_action_stop_v2ray),
|
||||||
stopV2RayPendingIntent)
|
stopV2RayPendingIntent
|
||||||
|
)
|
||||||
//.build()
|
//.build()
|
||||||
|
|
||||||
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
||||||
@@ -305,10 +331,12 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun createNotificationChannel(): String {
|
private fun createNotificationChannel(): String {
|
||||||
val channelId = "RAY_NG_M_CH_ID"
|
val channelId = AppConfig.RAY_NG_CHANNEL_ID
|
||||||
val channelName = "V2rayNG Background Service"
|
val channelName = AppConfig.RAY_NG_CHANNEL_NAME
|
||||||
val chan = NotificationChannel(channelId,
|
val chan = NotificationChannel(
|
||||||
channelName, NotificationManager.IMPORTANCE_HIGH)
|
channelId,
|
||||||
|
channelName, NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
chan.lightColor = Color.DKGRAY
|
chan.lightColor = Color.DKGRAY
|
||||||
chan.importance = NotificationManager.IMPORTANCE_NONE
|
chan.importance = NotificationManager.IMPORTANCE_NONE
|
||||||
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||||
@@ -320,8 +348,8 @@ object V2RayServiceManager {
|
|||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
service.stopForeground(true)
|
service.stopForeground(true)
|
||||||
mBuilder = null
|
mBuilder = null
|
||||||
mSubscription?.unsubscribe()
|
mDisposable?.dispose()
|
||||||
mSubscription = null
|
mDisposable = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
||||||
@@ -348,36 +376,39 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startSpeedNotification() {
|
private fun startSpeedNotification() {
|
||||||
if (mSubscription == null &&
|
if (mDisposable == null &&
|
||||||
v2rayPoint.isRunning &&
|
v2rayPoint.isRunning &&
|
||||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true
|
||||||
|
) {
|
||||||
var lastZeroSpeed = false
|
var lastZeroSpeed = false
|
||||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||||
outboundTags?.remove(TAG_DIRECT)
|
outboundTags?.remove(TAG_DIRECT)
|
||||||
|
|
||||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||||
.subscribe {
|
.subscribe {
|
||||||
val queryTime = System.currentTimeMillis()
|
val queryTime = System.currentTimeMillis()
|
||||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||||
var proxyTotal = 0L
|
var proxyTotal = 0L
|
||||||
val text = StringBuilder()
|
val text = StringBuilder()
|
||||||
outboundTags?.forEach {
|
outboundTags?.forEach {
|
||||||
val up = v2rayPoint.queryStats(it, "uplink")
|
val up = v2rayPoint.queryStats(it, AppConfig.UPLINK)
|
||||||
val down = v2rayPoint.queryStats(it, "downlink")
|
val down = v2rayPoint.queryStats(it, AppConfig.DOWNLINK)
|
||||||
if (up + down > 0) {
|
if (up + down > 0) {
|
||||||
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
||||||
proxyTotal += up + down
|
proxyTotal += up + down
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
|
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.UPLINK)
|
||||||
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
|
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.DOWNLINK)
|
||||||
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
|
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
|
||||||
if (!zeroSpeed || !lastZeroSpeed) {
|
if (!zeroSpeed || !lastZeroSpeed) {
|
||||||
if (proxyTotal == 0L) {
|
if (proxyTotal == 0L) {
|
||||||
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
|
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
|
||||||
}
|
}
|
||||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
appendSpeedString(
|
||||||
directDownlink / sinceLastQueryInSeconds)
|
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||||
|
directDownlink / sinceLastQueryInSeconds
|
||||||
|
)
|
||||||
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
||||||
}
|
}
|
||||||
lastZeroSpeed = zeroSpeed
|
lastZeroSpeed = zeroSpeed
|
||||||
@@ -397,9 +428,9 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun stopSpeedNotification() {
|
private fun stopSpeedNotification() {
|
||||||
if (mSubscription != null) {
|
if (mDisposable != null) {
|
||||||
mSubscription?.unsubscribe() //stop queryStats
|
mDisposable?.dispose() //stop queryStats
|
||||||
mSubscription = null
|
mDisposable = null
|
||||||
updateNotification(currentConfig?.remarks, 0, 0)
|
updateNotification(currentConfig?.remarks, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import com.v2ray.ang.util.MessageUtil
|
|||||||
import com.v2ray.ang.util.SpeedtestUtil
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import go.Seq
|
import go.Seq
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@@ -32,6 +36,7 @@ class V2RayTestService : Service() {
|
|||||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MSG_MEASURE_CONFIG_CANCEL -> {
|
MSG_MEASURE_CONFIG_CANCEL -> {
|
||||||
realTestScope.coroutineContext[Job]?.cancelChildren()
|
realTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import android.app.Service
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.*
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.LocalSocket
|
||||||
|
import android.net.LocalSocketAddress
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkRequest
|
||||||
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
@@ -17,8 +23,8 @@ import com.v2ray.ang.dto.ERoutingMode
|
|||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.MyContextWrapper
|
import com.v2ray.ang.util.MyContextWrapper
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
@@ -196,14 +202,16 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
|
|
||||||
private fun runTun2socks() {
|
private fun runTun2socks() {
|
||||||
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||||
val cmd = arrayListOf(File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
val cmd = arrayListOf(
|
||||||
|
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||||
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
||||||
"--netif-netmask", "255.255.255.252",
|
"--netif-netmask", "255.255.255.252",
|
||||||
"--socks-server-addr", "127.0.0.1:${socksPort}",
|
"--socks-server-addr", "127.0.0.1:${socksPort}",
|
||||||
"--tunmtu", VPN_MTU.toString(),
|
"--tunmtu", VPN_MTU.toString(),
|
||||||
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
|
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
|
||||||
"--enable-udprelay",
|
"--enable-udprelay",
|
||||||
"--loglevel", "notice")
|
"--loglevel", "notice"
|
||||||
|
)
|
||||||
|
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||||
cmd.add("--netif-ip6addr")
|
cmd.add("--netif-ip6addr")
|
||||||
@@ -223,11 +231,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
.directory(applicationContext.filesDir)
|
.directory(applicationContext.filesDir)
|
||||||
.start()
|
.start()
|
||||||
Thread(Runnable {
|
Thread(Runnable {
|
||||||
Log.d(packageName,"$TUN2SOCKS check")
|
Log.d(packageName, "$TUN2SOCKS check")
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
Log.d(packageName,"$TUN2SOCKS exited")
|
Log.d(packageName, "$TUN2SOCKS exited")
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
Log.d(packageName,"$TUN2SOCKS restart")
|
Log.d(packageName, "$TUN2SOCKS restart")
|
||||||
runTun2socks()
|
runTun2socks()
|
||||||
}
|
}
|
||||||
}).start()
|
}).start()
|
||||||
@@ -244,7 +252,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
||||||
Log.d(packageName, path)
|
Log.d(packageName, path)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
var tries = 0
|
var tries = 0
|
||||||
while (true) try {
|
while (true) try {
|
||||||
Thread.sleep(50L shl tries)
|
Thread.sleep(50L shl tries)
|
||||||
@@ -274,7 +282,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
// val emptyInfo = VpnNetworkInfo()
|
// val emptyInfo = VpnNetworkInfo()
|
||||||
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
||||||
// saveVpnNetworkInfo(configName, info)
|
// saveVpnNetworkInfo(configName, info)
|
||||||
isRunning = false;
|
isRunning = false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
try {
|
try {
|
||||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||||
@@ -327,7 +335,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
val context = newBase?.let {
|
val context = newBase?.let {
|
||||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||||
}
|
}
|
||||||
super.attachBaseContext(context)
|
super.attachBaseContext(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import androidx.core.content.FileProvider
|
||||||
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
@@ -20,20 +21,42 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class AboutActivity : BaseActivity() {
|
class AboutActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityAboutBinding
|
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
||||||
private val extDir by lazy { File(Utils.backupPath(this)) }
|
private val extDir by lazy { File(Utils.backupPath(this)) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityAboutBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
title = getString(R.string.title_about)
|
title = getString(R.string.title_about)
|
||||||
|
|
||||||
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
||||||
binding.layoutBackup.setOnClickListener {
|
binding.layoutBackup.setOnClickListener {
|
||||||
backupMMKV()
|
val ret = backupConfiguration(extDir.absolutePath)
|
||||||
|
if (ret.first) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.layoutShare.setOnClickListener {
|
||||||
|
val ret = backupConfiguration(cacheDir.absolutePath)
|
||||||
|
if (ret.first) {
|
||||||
|
startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
Intent(Intent.ACTION_SEND).setType("application/zip")
|
||||||
|
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.putExtra(
|
||||||
|
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
|
||||||
|
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
|
||||||
|
)
|
||||||
|
), getString(R.string.title_configuration_share)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutRestore.setOnClickListener {
|
binding.layoutRestore.setOnClickListener {
|
||||||
@@ -77,40 +100,36 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupMMKV() {
|
fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
|
||||||
val dateFormated = SimpleDateFormat(
|
val dateFormated = SimpleDateFormat(
|
||||||
"yyyy-MM-dd-HH-mm-ss",
|
"yyyy-MM-dd-HH-mm-ss",
|
||||||
Locale.getDefault()
|
Locale.getDefault()
|
||||||
).format(System.currentTimeMillis())
|
).format(System.currentTimeMillis())
|
||||||
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
|
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
|
||||||
val backupDir = this.cacheDir.absolutePath + "/$folderName"
|
val backupDir = this.cacheDir.absolutePath + "/$folderName"
|
||||||
val outputZipFilePath = extDir.absolutePath + "/$folderName.zip"
|
val outputZipFilePath = "$outputZipFilePos/$folderName.zip"
|
||||||
|
|
||||||
val count = MMKV.backupAllToDirectory(backupDir)
|
val count = MMKV.backupAllToDirectory(backupDir)
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
toast(R.string.toast_failure)
|
return Pair(false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
|
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
|
||||||
toast(R.string.toast_success)
|
return Pair(true, outputZipFilePath)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
return Pair(false, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreMMKV(zipFile: File) {
|
fun restoreConfiguration(zipFile: File): Boolean {
|
||||||
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
||||||
|
|
||||||
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
||||||
toast(R.string.toast_failure)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val count = MMKV.restoreAllFromDirectory(backupDir)
|
val count = MMKV.restoreAllFromDirectory(backupDir)
|
||||||
if (count > 0) {
|
return count > 0
|
||||||
toast(R.string.toast_success)
|
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFileChooser() {
|
private fun showFileChooser() {
|
||||||
@@ -138,8 +157,11 @@ class AboutActivity : BaseActivity() {
|
|||||||
input?.copyTo(fileOut)
|
input?.copyTo(fileOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (restoreConfiguration(targetFile)) {
|
||||||
restoreMMKV(targetFile)
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,19 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
onBackPressed()
|
// Handles the home button press by delegating to the onBackPressedDispatcher.
|
||||||
|
// This ensures consistent back navigation behavior.
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
val context = newBase?.let {
|
val context = newBase?.let {
|
||||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||||
}
|
}
|
||||||
super.attachBaseContext(context)
|
super.attachBaseContext(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -14,20 +14,18 @@ import com.v2ray.ang.databinding.ActivityLogcatBinding
|
|||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedHashSet
|
|
||||||
|
|
||||||
class LogcatActivity : BaseActivity() {
|
class LogcatActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityLogcatBinding
|
private val binding by lazy {
|
||||||
|
ActivityLogcatBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
title = getString(R.string.title_logcat)
|
title = getString(R.string.title_logcat)
|
||||||
|
|
||||||
@@ -44,9 +42,11 @@ class LogcatActivity : BaseActivity() {
|
|||||||
val lst = LinkedHashSet<String>()
|
val lst = LinkedHashSet<String>()
|
||||||
lst.add("logcat")
|
lst.add("logcat")
|
||||||
lst.add("-c")
|
lst.add("-c")
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val lst = LinkedHashSet<String>()
|
val lst = LinkedHashSet<String>()
|
||||||
lst.add("logcat")
|
lst.add("logcat")
|
||||||
lst.add("-d")
|
lst.add("-d")
|
||||||
@@ -54,7 +54,9 @@ class LogcatActivity : BaseActivity() {
|
|||||||
lst.add("time")
|
lst.add("time")
|
||||||
lst.add("-s")
|
lst.add("-s")
|
||||||
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
|
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
|
||||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
val process = withContext(Dispatchers.IO) {
|
||||||
|
Runtime.getRuntime().exec(lst.toTypedArray())
|
||||||
|
}
|
||||||
// val bufferedReader = BufferedReader(
|
// val bufferedReader = BufferedReader(
|
||||||
// InputStreamReader(process.inputStream))
|
// InputStreamReader(process.inputStream))
|
||||||
// val allText = bufferedReader.use(BufferedReader::readText)
|
// val allText = bufferedReader.use(BufferedReader::readText)
|
||||||
@@ -82,10 +84,12 @@ class LogcatActivity : BaseActivity() {
|
|||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.clear_all -> {
|
R.id.clear_all -> {
|
||||||
logcat(true)
|
logcat(true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.net.VpnService
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -19,16 +18,17 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
@@ -39,41 +39,55 @@ import com.v2ray.ang.util.AngConfigManager
|
|||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.viewmodel.MainViewModel
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.drakeet.support.toast.ToastCompat
|
import me.drakeet.support.toast.ToastCompat
|
||||||
import rx.Observable
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private val binding by lazy {
|
||||||
|
ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
private val adapter by lazy { MainRecyclerAdapter(this) }
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
|
||||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
|
||||||
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
startV2Ray()
|
startV2Ray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private val requestSubSettingActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
initGroupTab()
|
||||||
|
}
|
||||||
|
private val tabGroupListener = object : TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
|
val selectId = tab?.tag.toString()
|
||||||
|
if (selectId != mainViewModel.subscriptionId) {
|
||||||
|
mainViewModel.subscriptionIdChanged(selectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||||
|
}
|
||||||
|
}
|
||||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||||
val mainViewModel: MainViewModel by viewModels()
|
val mainViewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
title = getString(R.string.title_server)
|
title = getString(R.string.title_server)
|
||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
|
|
||||||
binding.fab.setOnClickListener {
|
binding.fab.setOnClickListener {
|
||||||
if (mainViewModel.isRunning.value == true) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
Utils.stopVService(this)
|
Utils.stopVService(this)
|
||||||
} else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
} else if ((MmkvManager.settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||||
val intent = VpnService.prepare(this)
|
val intent = VpnService.prepare(this)
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
startV2Ray()
|
startV2Ray()
|
||||||
@@ -103,15 +117,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
|
|
||||||
|
|
||||||
val toggle = ActionBarDrawerToggle(
|
val toggle = ActionBarDrawerToggle(
|
||||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||||
|
)
|
||||||
binding.drawerLayout.addDrawerListener(toggle)
|
binding.drawerLayout.addDrawerListener(toggle)
|
||||||
toggle.syncState()
|
toggle.syncState()
|
||||||
binding.navView.setNavigationItemSelectedListener(this)
|
binding.navView.setNavigationItemSelectedListener(this)
|
||||||
|
|
||||||
|
initGroupTab()
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
copyAssets()
|
|
||||||
//migrateLegacy()
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
RxPermissions(this)
|
RxPermissions(this)
|
||||||
@@ -158,49 +171,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mainViewModel.startListenBroadcast()
|
mainViewModel.startListenBroadcast()
|
||||||
|
mainViewModel.copyAssets(assets)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyAssets() {
|
private fun initGroupTab() {
|
||||||
val extFolder = Utils.userAssetPath(this)
|
binding.tabGroup.removeOnTabSelectedListener(tabGroupListener)
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
binding.tabGroup.removeAllTabs()
|
||||||
try {
|
binding.tabGroup.isVisible = false
|
||||||
val geo = arrayOf("geosite.dat", "geoip.dat")
|
|
||||||
assets.list("")
|
val (listId, listRemarks) = mainViewModel.getSubscriptions(this)
|
||||||
?.filter { geo.contains(it) }
|
if (listId == null || listRemarks == null) {
|
||||||
?.filter { !File(extFolder, it).exists() }
|
return
|
||||||
?.forEach {
|
|
||||||
val target = File(extFolder, it)
|
|
||||||
assets.open(it).use { input ->
|
|
||||||
FileOutputStream(target).use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(ANG_PACKAGE, "Copied from apk assets folder to ${target.absolutePath}")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun migrateLegacy() {
|
for (it in listRemarks.indices) {
|
||||||
// lifecycleScope.launch(Dispatchers.IO) {
|
val tab = binding.tabGroup.newTab()
|
||||||
// val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
tab.text = listRemarks[it]
|
||||||
// if (result != null) {
|
tab.tag = listId[it]
|
||||||
// launch(Dispatchers.Main) {
|
binding.tabGroup.addTab(tab)
|
||||||
// if (result) {
|
}
|
||||||
// toast(getString(R.string.migration_success))
|
val selectIndex =
|
||||||
// mainViewModel.reloadServerList()
|
listId.indexOf(mainViewModel.subscriptionId).takeIf { it >= 0 } ?: (listId.count() - 1)
|
||||||
// } else {
|
binding.tabGroup.selectTab(binding.tabGroup.getTabAt(selectIndex))
|
||||||
// toast(getString(R.string.migration_fail))
|
binding.tabGroup.addOnTabSelectedListener(tabGroupListener)
|
||||||
// }
|
binding.tabGroup.isVisible = true
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun startV2Ray() {
|
fun startV2Ray() {
|
||||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
if (MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
V2RayServiceManager.startV2Ray(this)
|
V2RayServiceManager.startV2Ray(this)
|
||||||
@@ -228,7 +226,25 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
return true
|
|
||||||
|
val searchItem = menu.findItem(R.id.search_view)
|
||||||
|
if (searchItem != null) {
|
||||||
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
mainViewModel.filterConfig(newText.orEmpty())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
searchView.setOnCloseListener {
|
||||||
|
mainViewModel.filterConfig("")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
@@ -236,67 +252,80 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
importQRcode(true)
|
importQRcode(true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_clipboard -> {
|
R.id.import_clipboard -> {
|
||||||
importClipboard()
|
importClipboard()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_vmess -> {
|
R.id.import_manually_vmess -> {
|
||||||
importManually(EConfigType.VMESS.value)
|
importManually(EConfigType.VMESS.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_vless -> {
|
R.id.import_manually_vless -> {
|
||||||
importManually(EConfigType.VLESS.value)
|
importManually(EConfigType.VLESS.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_ss -> {
|
R.id.import_manually_ss -> {
|
||||||
importManually(EConfigType.SHADOWSOCKS.value)
|
importManually(EConfigType.SHADOWSOCKS.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_socks -> {
|
R.id.import_manually_socks -> {
|
||||||
importManually(EConfigType.SOCKS.value)
|
importManually(EConfigType.SOCKS.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_trojan -> {
|
R.id.import_manually_trojan -> {
|
||||||
importManually(EConfigType.TROJAN.value)
|
importManually(EConfigType.TROJAN.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_manually_wireguard -> {
|
R.id.import_manually_wireguard -> {
|
||||||
importManually(EConfigType.WIREGUARD.value)
|
importManually(EConfigType.WIREGUARD.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_config_custom_clipboard -> {
|
R.id.import_config_custom_clipboard -> {
|
||||||
importConfigCustomClipboard()
|
importConfigCustomClipboard()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_config_custom_local -> {
|
R.id.import_config_custom_local -> {
|
||||||
importConfigCustomLocal()
|
importConfigCustomLocal()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_config_custom_url -> {
|
R.id.import_config_custom_url -> {
|
||||||
importConfigCustomUrlClipboard()
|
importConfigCustomUrlClipboard()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_config_custom_url_scan -> {
|
R.id.import_config_custom_url_scan -> {
|
||||||
importQRcode(false)
|
importQRcode(false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// R.id.sub_setting -> {
|
|
||||||
// startActivity<SubSettingActivity>()
|
|
||||||
// true
|
|
||||||
// }
|
|
||||||
|
|
||||||
R.id.sub_update -> {
|
R.id.sub_update -> {
|
||||||
importConfigViaSub()
|
importConfigViaSub()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.export_all -> {
|
R.id.export_all -> {
|
||||||
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
|
binding.pbWaiting.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val ret = mainViewModel.exportAllServer()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (ret == 0)
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
} else {
|
else
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,52 +347,77 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
R.id.del_all_config -> {
|
R.id.del_all_config -> {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
MmkvManager.removeAllServer()
|
binding.pbWaiting.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
mainViewModel.removeAllServer()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.del_duplicate_config-> {
|
|
||||||
|
R.id.del_duplicate_config -> {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
mainViewModel.removeDuplicateServer()
|
binding.pbWaiting.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val ret = mainViewModel.removeDuplicateServer()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
|
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
||||||
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.del_invalid_config -> {
|
R.id.del_invalid_config -> {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_invalid_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
MmkvManager.removeInvalidServer()
|
binding.pbWaiting.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
mainViewModel.removeInvalidServer()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_test_results -> {
|
R.id.sort_by_test_results -> {
|
||||||
MmkvManager.sortByTestResults()
|
binding.pbWaiting.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
mainViewModel.sortByTestResults()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
true
|
binding.pbWaiting.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
R.id.filter_config -> {
|
|
||||||
mainViewModel.filterConfig(this)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importManually(createConfigType : Int) {
|
private fun importManually(createConfigType: Int) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent()
|
Intent()
|
||||||
.putExtra("createConfigType", createConfigType)
|
.putExtra("createConfigType", createConfigType)
|
||||||
@@ -375,7 +429,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from qrcode
|
* import config from qrcode
|
||||||
*/
|
*/
|
||||||
fun importQRcode(forConfig: Boolean): Boolean {
|
private fun importQRcode(forConfig: Boolean): Boolean {
|
||||||
// try {
|
// try {
|
||||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
// .addCategory(Intent.CATEGORY_DEFAULT)
|
||||||
@@ -411,7 +465,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from clipboard
|
* import config from clipboard
|
||||||
*/
|
*/
|
||||||
fun importClipboard()
|
private fun importClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val clipboard = Utils.getClipboard(this)
|
val clipboard = Utils.getClipboard(this)
|
||||||
@@ -423,30 +477,32 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(server: String?, subid: String = "") {
|
private fun importBatchConfig(server: String?) {
|
||||||
val subid2 = if(subid.isNullOrEmpty()){
|
// val dialog = AlertDialog.Builder(this)
|
||||||
mainViewModel.subscriptionId
|
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
}else{
|
// .setCancelable(false)
|
||||||
subid
|
// .show()
|
||||||
}
|
binding.pbWaiting.show()
|
||||||
val append = subid.isNullOrEmpty()
|
|
||||||
|
|
||||||
var count = AngConfigManager.importBatchConfig(server, subid2, append)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (count <= 0) {
|
val (count, countSub) = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
|
||||||
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
|
delay(500L)
|
||||||
}
|
launch(Dispatchers.Main) {
|
||||||
if (count <= 0) {
|
|
||||||
count = AngConfigManager.appendCustomConfigServer(server, subid2)
|
|
||||||
}
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
} else if (countSub > 0) {
|
||||||
|
initGroupTab()
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
|
//dialog.dismiss()
|
||||||
|
binding.pbWaiting.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importConfigCustomClipboard()
|
private fun importConfigCustomClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val configText = Utils.getClipboard(this)
|
val configText = Utils.getClipboard(this)
|
||||||
@@ -465,7 +521,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from local config file
|
* import config from local config file
|
||||||
*/
|
*/
|
||||||
fun importConfigCustomLocal(): Boolean {
|
private fun importConfigCustomLocal(): Boolean {
|
||||||
try {
|
try {
|
||||||
showFileChooser()
|
showFileChooser()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -475,7 +531,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importConfigCustomUrlClipboard()
|
private fun importConfigCustomUrlClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val url = Utils.getClipboard(this)
|
val url = Utils.getClipboard(this)
|
||||||
@@ -493,7 +549,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from url
|
* import config from url
|
||||||
*/
|
*/
|
||||||
fun importConfigCustomUrl(url: String?): Boolean {
|
private fun importConfigCustomUrl(url: String?): Boolean {
|
||||||
try {
|
try {
|
||||||
if (!Utils.isValidUrl(url)) {
|
if (!Utils.isValidUrl(url)) {
|
||||||
toast(R.string.toast_invalid_url)
|
toast(R.string.toast_invalid_url)
|
||||||
@@ -520,43 +576,26 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from sub
|
* import config from sub
|
||||||
*/
|
*/
|
||||||
fun importConfigViaSub()
|
private fun importConfigViaSub(): Boolean {
|
||||||
: Boolean {
|
// val dialog = AlertDialog.Builder(this)
|
||||||
try {
|
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
toast(R.string.title_sub_update)
|
// .setCancelable(false)
|
||||||
MmkvManager.decodeSubscriptions().forEach {
|
// .show()
|
||||||
if (TextUtils.isEmpty(it.first)
|
binding.pbWaiting.show()
|
||||||
|| TextUtils.isEmpty(it.second.remarks)
|
|
||||||
|| TextUtils.isEmpty(it.second.url)
|
|
||||||
) {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
if (!it.second.enabled) {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val url = Utils.idnToASCII(it.second.url)
|
|
||||||
if (!Utils.isValidUrl(url)) {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
Log.d(ANG_PACKAGE, url)
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val configText = try {
|
val count = mainViewModel.updateConfigViaSubAll()
|
||||||
Utils.getUrlContentWithCustomUserAgent(url)
|
delay(500L)
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
|
if (count > 0) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
return@launch
|
//dialog.dismiss()
|
||||||
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
importBatchConfig(configText, it.first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -611,15 +650,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import customize config
|
* import customize config
|
||||||
*/
|
*/
|
||||||
fun importCustomizeConfig(server: String?) {
|
private fun importCustomizeConfig(server: String?) {
|
||||||
try {
|
try {
|
||||||
if (server == null || TextUtils.isEmpty(server)) {
|
if (server == null || TextUtils.isEmpty(server)) {
|
||||||
toast(R.string.toast_none_data)
|
toast(R.string.toast_none_data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainViewModel.appendCustomConfigServer(server)
|
if (mainViewModel.appendCustomConfigServer(server)) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
@@ -628,7 +670,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTestState(content: String?) {
|
private fun setTestState(content: String?) {
|
||||||
binding.tvTestState.text = content
|
binding.tvTestState.text = content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,27 +691,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||||
// Handle navigation view item clicks here.
|
// Handle navigation view item clicks here.
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
//R.id.server_profile -> activityClass = MainActivity::class.java
|
|
||||||
R.id.sub_setting -> {
|
R.id.sub_setting -> {
|
||||||
startActivity(Intent(this, SubSettingActivity::class.java))
|
requestSubSettingActivity.launch(Intent(this, SubSettingActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.settings -> {
|
R.id.settings -> {
|
||||||
startActivity(Intent(this, SettingsActivity::class.java)
|
startActivity(
|
||||||
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
Intent(this, SettingsActivity::class.java)
|
||||||
|
.putExtra("isRunning", mainViewModel.isRunning.value == true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.user_asset_setting -> {
|
R.id.user_asset_setting -> {
|
||||||
startActivity(Intent(this, UserAssetActivity::class.java))
|
startActivity(Intent(this, UserAssetActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.promotion -> {
|
R.id.promotion -> {
|
||||||
Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.logcat -> {
|
R.id.logcat -> {
|
||||||
startActivity(Intent(this, LogcatActivity::class.java))
|
startActivity(Intent(this, LogcatActivity::class.java))
|
||||||
}
|
}
|
||||||
R.id.about-> {
|
|
||||||
|
R.id.about -> {
|
||||||
startActivity(Intent(this, AboutActivity::class.java))
|
startActivity(Intent(this, AboutActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
|
||||||
import com.v2ray.ang.AngApplication.Companion.application
|
import com.v2ray.ang.AngApplication.Companion.application
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -26,21 +25,17 @@ import com.v2ray.ang.service.V2RayServiceManager
|
|||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import rx.Observable
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
|
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||||
, ItemTouchHelperAdapter {
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val VIEW_TYPE_ITEM = 1
|
private const val VIEW_TYPE_ITEM = 1
|
||||||
private const val VIEW_TYPE_FOOTER = 2
|
private const val VIEW_TYPE_FOOTER = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mActivity: MainActivity = activity
|
private var mActivity: MainActivity = activity
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
|
||||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
|
||||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
|
||||||
private val share_method: Array<out String> by lazy {
|
private val share_method: Array<out String> by lazy {
|
||||||
mActivity.resources.getStringArray(R.array.share_method)
|
mActivity.resources.getStringArray(R.array.share_method)
|
||||||
}
|
}
|
||||||
@@ -51,7 +46,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||||
if (holder is MainViewHolder) {
|
if (holder is MainViewHolder) {
|
||||||
val guid = mActivity.mainViewModel.serversCache[position].guid
|
val guid = mActivity.mainViewModel.serversCache[position].guid
|
||||||
val config = mActivity.mainViewModel.serversCache[position].config
|
val profile = mActivity.mainViewModel.serversCache[position].profile
|
||||||
// //filter
|
// //filter
|
||||||
// if (mActivity.mainViewModel.subscriptionId.isNotEmpty()
|
// if (mActivity.mainViewModel.subscriptionId.isNotEmpty()
|
||||||
// && mActivity.mainViewModel.subscriptionId != config.subscriptionId
|
// && mActivity.mainViewModel.subscriptionId != config.subscriptionId
|
||||||
@@ -61,43 +56,46 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
// holder.itemMainBinding.cardView.visibility = View.VISIBLE
|
// holder.itemMainBinding.cardView.visibility = View.VISIBLE
|
||||||
// }
|
// }
|
||||||
|
|
||||||
val outbound = config.getProxyOutbound()
|
|
||||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||||
|
|
||||||
holder.itemMainBinding.tvName.text = config.remarks
|
holder.itemMainBinding.tvName.text = profile.remarks
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty()
|
||||||
if (aff?.testDelayMillis ?: 0L < 0L) {
|
if ((aff?.testDelayMillis ?: 0L) < 0L) {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
||||||
} else {
|
} else {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||||
}
|
}
|
||||||
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid == MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
|
||||||
} else {
|
} else {
|
||||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
|
||||||
}
|
}
|
||||||
holder.itemMainBinding.tvSubscription.text = ""
|
holder.itemMainBinding.tvSubscription.text = ""
|
||||||
val json = subStorage?.decodeString(config.subscriptionId)
|
val json = MmkvManager.subStorage?.decodeString(profile.subscriptionId)
|
||||||
if (!json.isNullOrBlank()) {
|
if (!json.isNullOrBlank()) {
|
||||||
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||||
holder.itemMainBinding.tvSubscription.text = sub.remarks
|
holder.itemMainBinding.tvSubscription.text = sub.remarks
|
||||||
}
|
}
|
||||||
|
|
||||||
var shareOptions = share_method.asList()
|
var shareOptions = share_method.asList()
|
||||||
when (config.configType) {
|
when (profile.configType) {
|
||||||
EConfigType.CUSTOM -> {
|
EConfigType.CUSTOM -> {
|
||||||
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
||||||
shareOptions = shareOptions.takeLast(1)
|
shareOptions = shareOptions.takeLast(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
EConfigType.VLESS -> {
|
EConfigType.VLESS -> {
|
||||||
holder.itemMainBinding.tvType.text = config.configType.name
|
holder.itemMainBinding.tvType.text = profile.configType.name
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
holder.itemMainBinding.tvType.text = profile.configType.name.lowercase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val strState = "${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
|
|
||||||
|
val strState = "${profile.server?.dropLast(3)}*** : ${profile.serverPort}"
|
||||||
|
|
||||||
holder.itemMainBinding.tvStatistics.text = strState
|
holder.itemMainBinding.tvStatistics.text = strState
|
||||||
|
|
||||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||||
@@ -105,7 +103,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
try {
|
try {
|
||||||
when (i) {
|
when (i) {
|
||||||
0 -> {
|
0 -> {
|
||||||
if (config.configType == EConfigType.CUSTOM) {
|
if (profile.configType == EConfigType.CUSTOM) {
|
||||||
shareFullContent(guid)
|
shareFullContent(guid)
|
||||||
} else {
|
} else {
|
||||||
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
||||||
@@ -113,6 +111,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||||
mActivity.toast(R.string.toast_success)
|
mActivity.toast(R.string.toast_success)
|
||||||
@@ -120,6 +119,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
mActivity.toast(R.string.toast_failure)
|
mActivity.toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> shareFullContent(guid)
|
2 -> shareFullContent(guid)
|
||||||
else -> mActivity.toast("else")
|
else -> mActivity.toast("else")
|
||||||
}
|
}
|
||||||
@@ -132,20 +132,20 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
holder.itemMainBinding.layoutEdit.setOnClickListener {
|
holder.itemMainBinding.layoutEdit.setOnClickListener {
|
||||||
val intent = Intent().putExtra("guid", guid)
|
val intent = Intent().putExtra("guid", guid)
|
||||||
.putExtra("isRunning", isRunning)
|
.putExtra("isRunning", isRunning)
|
||||||
if (config.configType == EConfigType.CUSTOM) {
|
if (profile.configType == EConfigType.CUSTOM) {
|
||||||
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||||
} else {
|
} else {
|
||||||
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
if (MmkvManager.settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||||
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
removeServer(guid, position)
|
removeServer(guid, position)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -158,11 +158,11 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
|
|
||||||
holder.itemMainBinding.infoContainer.setOnClickListener {
|
holder.itemMainBinding.infoContainer.setOnClickListener {
|
||||||
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
val selected = MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||||
if (guid != selected) {
|
if (guid != selected) {
|
||||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
MmkvManager.mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||||
if (!TextUtils.isEmpty(selected)) {
|
if (!TextUtils.isEmpty(selected)) {
|
||||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected!!))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(selected.orEmpty()))
|
||||||
}
|
}
|
||||||
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
@@ -196,7 +196,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeServer(guid: String,position:Int) {
|
private fun removeServer(guid: String, position: Int) {
|
||||||
mActivity.mainViewModel.removeServer(guid)
|
mActivity.mainViewModel.removeServer(guid)
|
||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
notifyItemRangeChanged(position, mActivity.mainViewModel.serversCache.size)
|
notifyItemRangeChanged(position, mActivity.mainViewModel.serversCache.size)
|
||||||
@@ -206,6 +206,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
VIEW_TYPE_ITEM ->
|
VIEW_TYPE_ITEM ->
|
||||||
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
}
|
}
|
||||||
@@ -237,7 +238,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
|
|
||||||
override fun onItemDismiss(position: Int) {
|
override fun onItemDismiss(position: Int) {
|
||||||
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
||||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
// mActivity.alert(R.string.del_config_comfirm) {
|
// mActivity.alert(R.string.del_config_comfirm) {
|
||||||
// positiveButton(android.R.string.ok) {
|
// positiveButton(android.R.string.ok) {
|
||||||
mActivity.mainViewModel.removeServer(guid)
|
mActivity.mainViewModel.removeServer(guid)
|
||||||
|
|||||||
@@ -21,14 +21,16 @@ import com.v2ray.ang.extension.v2RayApplication
|
|||||||
import com.v2ray.ang.util.AppManagerUtil
|
import com.v2ray.ang.util.AppManagerUtil
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
||||||
class PerAppProxyActivity : BaseActivity() {
|
class PerAppProxyActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityBypassListBinding
|
private val binding by lazy {
|
||||||
|
ActivityBypassListBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
private var adapter: PerAppProxyAdapter? = null
|
private var adapter: PerAppProxyAdapter? = null
|
||||||
private var appsAll: List<AppInfo>? = null
|
private var appsAll: List<AppInfo>? = null
|
||||||
@@ -36,9 +38,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityBypassListBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||||
binding.recyclerView.addItemDecoration(dividerItemDecoration)
|
binding.recyclerView.addItemDecoration(dividerItemDecoration)
|
||||||
@@ -188,12 +188,10 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
if (searchItem != null) {
|
if (searchItem != null) {
|
||||||
val searchView = searchItem.actionView as SearchView
|
val searchView = searchItem.actionView as SearchView
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
filterProxyApp(newText!!)
|
filterProxyApp(newText.orEmpty())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -209,29 +207,33 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
if (it.blacklist.containsAll(pkgNames)) {
|
if (it.blacklist.containsAll(pkgNames)) {
|
||||||
it.apps.forEach {
|
it.apps.forEach {
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
adapter?.blacklist!!.remove(packageName)
|
adapter?.blacklist?.remove(packageName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
it.apps.forEach {
|
it.apps.forEach {
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.notifyDataSetChanged()
|
it.notifyDataSetChanged()
|
||||||
true
|
true
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
||||||
R.id.select_proxy_app -> {
|
R.id.select_proxy_app -> {
|
||||||
selectProxyApp()
|
selectProxyApp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_proxy_app -> {
|
R.id.import_proxy_app -> {
|
||||||
importProxyApp()
|
importProxyApp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.export_proxy_app -> {
|
R.id.export_proxy_app -> {
|
||||||
exportProxyApp()
|
exportProxyApp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,9 +252,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun importProxyApp() {
|
private fun importProxyApp() {
|
||||||
val content = Utils.getClipboard(applicationContext)
|
val content = Utils.getClipboard(applicationContext)
|
||||||
if (TextUtils.isEmpty(content)) {
|
if (TextUtils.isEmpty(content)) return
|
||||||
return
|
|
||||||
}
|
|
||||||
selectProxyApp(content, false)
|
selectProxyApp(content, false)
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
}
|
}
|
||||||
@@ -274,11 +274,9 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(proxyApps)) {
|
if (TextUtils.isEmpty(proxyApps)) return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter?.blacklist!!.clear()
|
adapter?.blacklist?.clear()
|
||||||
|
|
||||||
if (binding.switchBypassApps.isChecked) {
|
if (binding.switchBypassApps.isChecked) {
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
@@ -286,7 +284,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (!inProxyApps(proxyApps, packageName, force)) {
|
if (!inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
}
|
}
|
||||||
@@ -299,7 +297,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (inProxyApps(proxyApps, packageName, force)) {
|
if (inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
}
|
}
|
||||||
@@ -316,12 +314,8 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun inProxyApps(proxyApps: String, packageName: String, force: Boolean): Boolean {
|
private fun inProxyApps(proxyApps: String, packageName: String, force: Boolean): Boolean {
|
||||||
if (force) {
|
if (force) {
|
||||||
if (packageName == "com.google.android.webview") {
|
if (packageName == "com.google.android.webview") return false
|
||||||
return false
|
if (packageName.startsWith("com.google")) return true
|
||||||
}
|
|
||||||
if (packageName.startsWith("com.google")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxyApps.indexOf(packageName) >= 0
|
return proxyApps.indexOf(packageName) >= 0
|
||||||
@@ -334,7 +328,8 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
if (key.isNotEmpty()) {
|
if (key.isNotEmpty()) {
|
||||||
appsAll?.forEach {
|
appsAll?.forEach {
|
||||||
if (it.appName.uppercase().indexOf(key) >= 0
|
if (it.appName.uppercase().indexOf(key) >= 0
|
||||||
|| it.packageName.uppercase().indexOf(key) >= 0) {
|
|| it.packageName.uppercase().indexOf(key) >= 0
|
||||||
|
) {
|
||||||
apps.add(it)
|
apps.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
|
import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
||||||
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
|
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
|
||||||
@@ -34,8 +33,10 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
VIEW_TYPE_HEADER -> {
|
VIEW_TYPE_HEADER -> {
|
||||||
val view = View(ctx)
|
val view = View(ctx)
|
||||||
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
view.layoutParams = ViewGroup.LayoutParams(
|
||||||
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0)
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0
|
||||||
|
)
|
||||||
BaseViewHolder(view)
|
BaseViewHolder(view)
|
||||||
}
|
}
|
||||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.v2ray.ang.R
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
|
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
|
||||||
|
|
||||||
class RoutingSettingsActivity : BaseActivity() {
|
class RoutingSettingsActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityRoutingSettingsBinding
|
private val binding by lazy { ActivityRoutingSettingsBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private val titles: Array<out String> by lazy {
|
private val titles: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.routing_tag)
|
resources.getStringArray(R.array.routing_tag)
|
||||||
@@ -16,9 +16,7 @@ class RoutingSettingsActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityRoutingSettingsBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
title = getString(R.string.title_pref_routing_custom)
|
title = getString(R.string.title_pref_routing_custom)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -23,17 +27,19 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RoutingSettingsFragment : Fragment() {
|
class RoutingSettingsFragment : Fragment() {
|
||||||
private lateinit var binding: FragmentRoutingSettingsBinding
|
private val binding by lazy { FragmentRoutingSettingsBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val routing_arg = "routing_arg"
|
private const val routing_arg = "routing_arg"
|
||||||
}
|
}
|
||||||
|
|
||||||
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) }
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(
|
||||||
savedInstanceState: Bundle?): View? {
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
binding = FragmentRoutingSettingsBinding.inflate(layoutInflater)
|
|
||||||
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +55,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
|
val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
|
||||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||||
|
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
@@ -64,22 +70,27 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
saveRouting()
|
saveRouting()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.del_routing -> {
|
R.id.del_routing -> {
|
||||||
binding.etRoutingContent.text = null
|
binding.etRoutingContent.text = null
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.scan_replace -> {
|
R.id.scan_replace -> {
|
||||||
scanQRcode(true)
|
scanQRcode(true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.scan_append -> {
|
R.id.scan_append -> {
|
||||||
scanQRcode(false)
|
scanQRcode(false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.default_rules -> {
|
R.id.default_rules -> {
|
||||||
setDefaultRules()
|
setDefaultRules()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +124,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +142,11 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
||||||
tag = AppConfig.TAG_PROXY
|
tag = AppConfig.TAG_PROXY
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
||||||
tag = AppConfig.TAG_DIRECT
|
tag = AppConfig.TAG_DIRECT
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
||||||
tag = AppConfig.TAG_BLOCKED
|
tag = AppConfig.TAG_BLOCKED
|
||||||
}
|
}
|
||||||
@@ -145,7 +158,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
val content = Utils.getUrlContext(url, 5000)
|
val content = Utils.getUrlContext(url, 5000)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
val routingList = if (TextUtils.isEmpty(content)) {
|
val routingList = if (TextUtils.isEmpty(content)) {
|
||||||
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag")
|
Utils.readTextFromAssets(activity?.v2RayApplication, "custom_routing_$tag")
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.*
|
import android.content.Intent
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
|
||||||
import com.v2ray.ang.R
|
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
|
|
||||||
class ScScannerActivity : BaseActivity() {
|
class ScScannerActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
|
|
||||||
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
|
val (count, countSub) = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
|
||||||
if (count > 0) {
|
if (count + countSub > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import com.v2ray.ang.R
|
|
||||||
import com.v2ray.ang.util.Utils
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.service.V2RayServiceManager
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
class ScSwitchActivity : BaseActivity() {
|
class ScSwitchActivity : BaseActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -20,7 +19,7 @@ import io.github.g00fy2.quickie.QRResult
|
|||||||
import io.github.g00fy2.quickie.ScanCustomCode
|
import io.github.g00fy2.quickie.ScanCustomCode
|
||||||
import io.github.g00fy2.quickie.config.ScannerConfig
|
import io.github.g00fy2.quickie.config.ScannerConfig
|
||||||
|
|
||||||
class ScannerActivity : BaseActivity(){
|
class ScannerActivity : BaseActivity() {
|
||||||
|
|
||||||
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
||||||
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) }
|
||||||
@@ -33,7 +32,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchScan(){
|
private fun launchScan() {
|
||||||
scanQrCode.launch(
|
scanQrCode.launch(
|
||||||
ScannerConfig.build {
|
ScannerConfig.build {
|
||||||
setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
|
setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
|
||||||
@@ -44,8 +43,8 @@ class ScannerActivity : BaseActivity(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResult(result: QRResult) {
|
private fun handleResult(result: QRResult) {
|
||||||
if (result is QRResult.QRSuccess ) {
|
if (result is QRResult.QRSuccess) {
|
||||||
finished(result.content.rawValue!!)
|
finished(result.content.rawValue.orEmpty())
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
@@ -54,7 +53,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
private fun finished(text: String) {
|
private fun finished(text: String) {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.putExtra("SCAN_RESULT", text)
|
intent.putExtra("SCAN_RESULT", text)
|
||||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +67,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
launchScan()
|
launchScan()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.select_photo -> {
|
R.id.select_photo -> {
|
||||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
Manifest.permission.READ_MEDIA_IMAGES
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
@@ -88,6 +88,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
try {
|
try {
|
||||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||||
finished(text!!)
|
finished(text.orEmpty())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
toast(e.message.toString())
|
toast(e.message.toString())
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import android.text.TextUtils
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AngApplication
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
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_V4
|
||||||
@@ -106,7 +110,9 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
||||||
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
||||||
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
||||||
|
private val tv_request_host: TextView? by lazy { findViewById(R.id.tv_request_host) }
|
||||||
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
||||||
|
private val tv_path: TextView? by lazy { findViewById(R.id.tv_path) }
|
||||||
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
||||||
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
||||||
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
|
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
|
||||||
@@ -158,6 +164,36 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_request_host?.text = Utils.getEditable(transportDetails[1])
|
et_request_host?.text = Utils.getEditable(transportDetails[1])
|
||||||
et_path?.text = Utils.getEditable(transportDetails[2])
|
et_path?.text = Utils.getEditable(transportDetails[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tv_request_host?.text = Utils.getEditable(
|
||||||
|
getString(
|
||||||
|
when (networks[position]) {
|
||||||
|
"tcp" -> R.string.server_lab_request_host_http
|
||||||
|
"ws" -> R.string.server_lab_request_host_ws
|
||||||
|
"httpupgrade" -> R.string.server_lab_request_host_httpupgrade
|
||||||
|
"splithttp" -> R.string.server_lab_request_host_splithttp
|
||||||
|
"h2" -> R.string.server_lab_request_host_h2
|
||||||
|
"quic" -> R.string.server_lab_request_host_quic
|
||||||
|
"grpc" -> R.string.server_lab_request_host_grpc
|
||||||
|
else -> R.string.server_lab_request_host
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tv_path?.text = Utils.getEditable(
|
||||||
|
getString(
|
||||||
|
when (networks[position]) {
|
||||||
|
"kcp" -> R.string.server_lab_path_kcp
|
||||||
|
"ws" -> R.string.server_lab_path_ws
|
||||||
|
"httpupgrade" -> R.string.server_lab_path_httpupgrade
|
||||||
|
"splithttp" -> R.string.server_lab_path_splithttp
|
||||||
|
"h2" -> R.string.server_lab_path_h2
|
||||||
|
"quic" -> R.string.server_lab_path_quic
|
||||||
|
"grpc" -> R.string.server_lab_path_grpc
|
||||||
|
else -> R.string.server_lab_path
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
@@ -286,7 +322,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
tlsSetting.alpn?.let {
|
tlsSetting.alpn?.let {
|
||||||
val alpnIndex = Utils.arrayFind(
|
val alpnIndex = Utils.arrayFind(
|
||||||
alpns,
|
alpns,
|
||||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||||
)
|
)
|
||||||
sp_stream_alpn?.setSelection(alpnIndex)
|
sp_stream_alpn?.setSelection(alpnIndex)
|
||||||
}
|
}
|
||||||
@@ -414,7 +450,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
saveStreamSettings(it)
|
saveStreamSettings(it)
|
||||||
}
|
}
|
||||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
config.subscriptionId = subscriptionId!!
|
config.subscriptionId = subscriptionId.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||||
import com.google.gson.*
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||||
@@ -21,7 +21,7 @@ import com.v2ray.ang.util.Utils
|
|||||||
import me.drakeet.support.toast.ToastCompat
|
import me.drakeet.support.toast.ToastCompat
|
||||||
|
|
||||||
class ServerCustomConfigActivity : BaseActivity() {
|
class ServerCustomConfigActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityServerCustomConfigBinding
|
private val binding by lazy { ActivityServerCustomConfigBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, 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 serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||||
@@ -34,9 +34,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityServerCustomConfigBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
title = getString(R.string.title_server)
|
title = getString(R.string.title_server)
|
||||||
|
|
||||||
if (!Utils.getDarkModeStatus(this)) {
|
if (!Utils.getDarkModeStatus(this)) {
|
||||||
@@ -91,7 +89,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
|
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
|
||||||
config.remarks = v2rayConfig.remarks ?: binding.etRemarks.text.toString().trim()
|
config.remarks = if (binding.etRemarks.text.isNullOrEmpty()) v2rayConfig.remarks.orEmpty() else binding.etRemarks.text.toString()
|
||||||
config.fullConfig = v2rayConfig
|
config.fullConfig = v2rayConfig
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
@@ -111,7 +109,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
MmkvManager.removeServer(editGuid)
|
MmkvManager.removeServer(editGuid)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -141,10 +139,12 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
deleteServer()
|
deleteServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.save_config -> {
|
R.id.save_config -> {
|
||||||
saveServer()
|
saveServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
||||||
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
||||||
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
||||||
|
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
|
||||||
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
||||||
|
|
||||||
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
||||||
@@ -143,18 +144,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// remoteDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
domesticDns?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// domesticDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
socksPort?.setOnPreferenceChangeListener { _, any ->
|
socksPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
||||||
@@ -165,6 +154,21 @@ class SettingsActivity : BaseActivity() {
|
|||||||
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
domesticDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
mode?.setOnPreferenceChangeListener { _, newValue ->
|
mode?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
updateMode(newValue.toString())
|
updateMode(newValue.toString())
|
||||||
true
|
true
|
||||||
@@ -194,13 +198,15 @@ class SettingsActivity : BaseActivity() {
|
|||||||
fragmentInterval?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
|
fragmentInterval?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
|
||||||
|
|
||||||
autoUpdateCheck?.isChecked = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
autoUpdateCheck?.isChecked = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||||
autoUpdateInterval?.summary = settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
|
autoUpdateInterval?.summary =
|
||||||
|
settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
|
||||||
autoUpdateInterval?.isEnabled = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
autoUpdateInterval?.isEnabled = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||||
|
|
||||||
socksPort?.summary = settingsStorage.decodeString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
socksPort?.summary = settingsStorage.decodeString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
||||||
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
||||||
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
||||||
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
||||||
|
delayTestUrl?.summary = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
|
||||||
|
|
||||||
initSharedPreference()
|
initSharedPreference()
|
||||||
}
|
}
|
||||||
@@ -217,7 +223,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
socksPort,
|
socksPort,
|
||||||
httpPort,
|
httpPort,
|
||||||
remoteDns,
|
remoteDns,
|
||||||
domesticDns
|
domesticDns,
|
||||||
|
delayTestUrl
|
||||||
).forEach { key ->
|
).forEach { key ->
|
||||||
key?.text = key?.summary.toString()
|
key?.text = key?.summary.toString()
|
||||||
}
|
}
|
||||||
@@ -230,6 +237,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
|
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||||
AppConfig.PREF_BYPASS_APPS,
|
AppConfig.PREF_BYPASS_APPS,
|
||||||
AppConfig.PREF_SPEED_ENABLED,
|
AppConfig.PREF_SPEED_ENABLED,
|
||||||
AppConfig.PREF_CONFIRM_REMOVE,
|
AppConfig.PREF_CONFIRM_REMOVE,
|
||||||
@@ -343,12 +351,15 @@ class SettingsActivity : BaseActivity() {
|
|||||||
updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
|
updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFragmentPackets(value: String?) {
|
private fun updateFragmentPackets(value: String?) {
|
||||||
fragmentPackets?.summary = value.toString()
|
fragmentPackets?.summary = value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFragmentLength(value: String?) {
|
private fun updateFragmentLength(value: String?) {
|
||||||
fragmentLength?.summary = value.toString()
|
fragmentLength?.summary = value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFragmentInterval(value: String?) {
|
private fun updateFragmentInterval(value: String?) {
|
||||||
fragmentInterval?.summary = value.toString()
|
fragmentInterval?.summary = value.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,25 +5,20 @@ import android.text.TextUtils
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.work.Constraints
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
|
||||||
import androidx.work.NetworkType
|
|
||||||
import androidx.work.PeriodicWorkRequest
|
|
||||||
import androidx.work.multiprocess.RemoteWorkManager
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AngApplication
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.service.SubscriptionUpdater
|
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.util.concurrent.TimeUnit
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SubEditActivity : BaseActivity() {
|
class SubEditActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivitySubEditBinding
|
private val binding by lazy { ActivitySubEditBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
var del_config: MenuItem? = null
|
||||||
var save_config: MenuItem? = null
|
var save_config: MenuItem? = null
|
||||||
@@ -33,9 +28,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivitySubEditBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
title = getString(R.string.title_sub_setting)
|
title = getString(R.string.title_sub_setting)
|
||||||
|
|
||||||
val json = subStorage?.decodeString(editSubId)
|
val json = subStorage?.decodeString(editSubId)
|
||||||
@@ -108,10 +101,14 @@ class SubEditActivity : BaseActivity() {
|
|||||||
if (editSubId.isNotEmpty()) {
|
if (editSubId.isNotEmpty()) {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
MmkvManager.removeSubscription(editSubId)
|
MmkvManager.removeSubscription(editSubId)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -136,10 +133,12 @@ class SubEditActivity : BaseActivity() {
|
|||||||
deleteServer()
|
deleteServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.save_config -> {
|
R.id.save_config -> {
|
||||||
saveServer()
|
saveServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import android.os.Bundle
|
|
||||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||||
|
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SubSettingActivity : BaseActivity() {
|
class SubSettingActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivitySubSettingBinding
|
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
|
var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
|
||||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
title = getString(R.string.title_sub_setting)
|
title = getString(R.string.title_sub_setting)
|
||||||
|
|
||||||
@@ -37,9 +43,6 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
||||||
menu.findItem(R.id.del_config)?.isVisible = false
|
|
||||||
menu.findItem(R.id.save_config)?.isVisible = false
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +51,30 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
startActivity(Intent(this, SubEditActivity::class.java))
|
startActivity(Intent(this, SubEditActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.sub_update -> {
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val count = AngConfigManager.updateConfigViaSubAll()
|
||||||
|
delay(500L)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (count > 0) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.ListView
|
|
||||||
import java.util.ArrayList
|
|
||||||
import com.v2ray.ang.R
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ListView
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
|
||||||
class TaskerActivity : BaseActivity() {
|
class TaskerActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityTaskerBinding
|
private val binding by lazy { ActivityTaskerBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private var listview: ListView? = null
|
private var listview: ListView? = null
|
||||||
private var lstData: ArrayList<String> = ArrayList()
|
private var lstData: ArrayList<String> = ArrayList()
|
||||||
@@ -27,9 +25,7 @@ class TaskerActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityTaskerBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
//add def value
|
//add def value
|
||||||
lstData.add("Default")
|
lstData.add("Default")
|
||||||
@@ -41,10 +37,12 @@ class TaskerActivity : BaseActivity() {
|
|||||||
lstGuid.add(key)
|
lstGuid.add(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val adapter = ArrayAdapter(this,
|
val adapter = ArrayAdapter(
|
||||||
android.R.layout.simple_list_item_single_choice, lstData)
|
this,
|
||||||
|
android.R.layout.simple_list_item_single_choice, lstData
|
||||||
|
)
|
||||||
listview = findViewById<View>(R.id.listview) as ListView
|
listview = findViewById<View>(R.id.listview) as ListView
|
||||||
listview!!.adapter = adapter
|
listview?.adapter = adapter
|
||||||
|
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
@@ -90,7 +88,7 @@ class TaskerActivity : BaseActivity() {
|
|||||||
|
|
||||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||||
intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb)
|
intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb)
|
||||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,10 +103,12 @@ class TaskerActivity : BaseActivity() {
|
|||||||
R.id.del_config -> {
|
R.id.del_config -> {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.save_config -> {
|
R.id.save_config -> {
|
||||||
confirmFinish()
|
confirmFinish()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.ui
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.v2ray.ang.AppConfig
|
import android.util.Log
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
@@ -11,45 +11,32 @@ import com.v2ray.ang.util.AngConfigManager
|
|||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
class UrlSchemeActivity : BaseActivity() {
|
class UrlSchemeActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityLogcatBinding
|
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
intent.apply {
|
intent.apply {
|
||||||
if (action == Intent.ACTION_SEND) {
|
if (action == Intent.ACTION_SEND) {
|
||||||
if ("text/plain" == type) {
|
if ("text/plain" == type) {
|
||||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
val uri = Uri.parse(it)
|
parseUri(it, null)
|
||||||
if (uri.scheme?.startsWith(AppConfig.PROTOCOL_HTTPS) == true || uri.scheme?.startsWith(
|
|
||||||
AppConfig.PROTOCOL_HTTP
|
|
||||||
) == true
|
|
||||||
) {
|
|
||||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
|
||||||
importSubscription(it, name)
|
|
||||||
} else {
|
|
||||||
importConfig(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (action == Intent.ACTION_VIEW) {
|
} else if (action == Intent.ACTION_VIEW) {
|
||||||
when (data?.host) {
|
when (data?.host) {
|
||||||
"install-config" -> {
|
"install-config" -> {
|
||||||
val uri: Uri? = intent.data
|
val uri: Uri? = intent.data
|
||||||
val shareUrl: String = uri?.getQueryParameter("url")!!
|
val shareUrl = uri?.getQueryParameter("url").orEmpty()
|
||||||
toast(shareUrl)
|
parseUri(shareUrl, uri?.fragment)
|
||||||
importConfig(shareUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"install-sub" -> {
|
"install-sub" -> {
|
||||||
val uri: Uri? = intent.data
|
val uri: Uri? = intent.data
|
||||||
val url = uri?.getQueryParameter("url")!!
|
val shareUrl = uri?.getQueryParameter("url").orEmpty()
|
||||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
parseUri(shareUrl, uri?.fragment)
|
||||||
importSubscription(url, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -57,10 +44,8 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
startActivity(Intent(this, MainActivity::class.java))
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -68,19 +53,25 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importSubscription(url: String, name: String) {
|
private fun parseUri(uriString: String?, fragment: String?) {
|
||||||
val decodedUrl = URLDecoder.decode(url, "UTF-8")
|
if (uriString.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
val check = AngConfigManager.importSubscription(name, decodedUrl)
|
|
||||||
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
|
|
||||||
}
|
}
|
||||||
|
Log.d("UrlScheme", uriString)
|
||||||
|
|
||||||
private fun importConfig(shareUrl: String) {
|
var decodedUrl = URLDecoder.decode(uriString, "UTF-8")
|
||||||
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
|
val uri = Uri.parse(decodedUrl)
|
||||||
if (count > 0) {
|
if (uri != null) {
|
||||||
toast(R.string.toast_success)
|
if (uri.fragment.isNullOrEmpty() && !fragment.isNullOrEmpty()) {
|
||||||
|
decodedUrl += "#${fragment}"
|
||||||
|
}
|
||||||
|
Log.d("UrlScheme-decodedUrl", decodedUrl)
|
||||||
|
val (count, countSub) = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||||
|
if (count + countSub > 0) {
|
||||||
|
toast(R.string.import_subscription_success)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.import_subscription_failure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,26 +2,31 @@ package com.v2ray.ang.ui
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||||
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
||||||
|
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||||
import com.v2ray.ang.dto.AssetUrlItem
|
import com.v2ray.ang.dto.AssetUrlItem
|
||||||
import com.v2ray.ang.extension.toTrafficString
|
import com.v2ray.ang.extension.toTrafficString
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
@@ -36,10 +41,10 @@ import java.net.InetSocketAddress
|
|||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
class UserAssetActivity : BaseActivity() {
|
class UserAssetActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivitySubSettingBinding
|
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||||
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 assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
@@ -49,9 +54,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
title = getString(R.string.title_user_asset_setting)
|
title = getString(R.string.title_user_asset_setting)
|
||||||
|
|
||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
@@ -80,6 +83,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.download_file -> {
|
R.id.download_file -> {
|
||||||
downloadGeoFiles()
|
downloadGeoFiles()
|
||||||
true
|
true
|
||||||
@@ -168,9 +172,13 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadGeoFiles() {
|
private fun downloadGeoFiles() {
|
||||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
toast(R.string.msg_downloading_content)
|
toast(R.string.msg_downloading_content)
|
||||||
|
|
||||||
|
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||||
var assets = MmkvManager.decodeAssetUrls()
|
var assets = MmkvManager.decodeAssetUrls()
|
||||||
assets = addBuiltInGeoItems(assets)
|
assets = addBuiltInGeoItems(assets)
|
||||||
|
|
||||||
@@ -188,6 +196,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
toast(getString(R.string.toast_failure) + " " + it.second.remarks)
|
toast(getString(R.string.toast_failure) + " " + it.second.remarks)
|
||||||
}
|
}
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,15 +238,18 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
conn?.disconnect()
|
conn?.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
|
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
|
||||||
val list = mutableListOf<Pair<String, AssetUrlItem>>()
|
val list = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||||
builtInGeoFiles
|
builtInGeoFiles
|
||||||
.filter { geoFile -> assets.none { it.second.remarks == geoFile } }
|
.filter { geoFile -> assets.none { it.second.remarks == geoFile } }
|
||||||
.forEach {
|
.forEach {
|
||||||
list.add(Utils.getUuid() to AssetUrlItem(
|
list.add(
|
||||||
|
Utils.getUuid() to AssetUrlItem(
|
||||||
it,
|
it,
|
||||||
AppConfig.GeoUrl + it
|
AppConfig.GeoUrl + it
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return list + assets
|
return list + assets
|
||||||
@@ -249,14 +261,15 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
ItemRecyclerUserAssetBinding.inflate(
|
ItemRecyclerUserAssetBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
parent,
|
parent,
|
||||||
false)
|
false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
|
||||||
var assets = MmkvManager.decodeAssetUrls();
|
var assets = MmkvManager.decodeAssetUrls()
|
||||||
assets = addBuiltInGeoItems(assets);
|
assets = addBuiltInGeoItems(assets)
|
||||||
val item = assets.getOrNull(position) ?: return
|
val item = assets.getOrNull(position) ?: return
|
||||||
// file with name == item.second.remarks
|
// file with name == item.second.remarks
|
||||||
val file = extDir.listFiles()?.find { it.name == item.second.remarks }
|
val file = extDir.listFiles()?.find { it.name == item.second.remarks }
|
||||||
@@ -292,8 +305,8 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
var assets = MmkvManager.decodeAssetUrls();
|
var assets = MmkvManager.decodeAssetUrls()
|
||||||
assets = addBuiltInGeoItems(assets);
|
assets = addBuiltInGeoItems(assets)
|
||||||
return assets.size
|
return assets.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import com.v2ray.ang.util.Utils
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class UserAssetUrlActivity : BaseActivity() {
|
class UserAssetUrlActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityUserAssetUrlBinding
|
private val binding by lazy { ActivityUserAssetUrlBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
var del_config: MenuItem? = null
|
||||||
var save_config: MenuItem? = null
|
var save_config: MenuItem? = null
|
||||||
@@ -27,9 +27,7 @@ class UserAssetUrlActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityUserAssetUrlBinding.inflate(layoutInflater)
|
setContentView(binding.root)
|
||||||
val view = binding.root
|
|
||||||
setContentView(view)
|
|
||||||
title = getString(R.string.title_user_asset_add_url)
|
title = getString(R.string.title_user_asset_add_url)
|
||||||
|
|
||||||
val json = assetStorage?.decodeString(editAssetId)
|
val json = assetStorage?.decodeString(editAssetId)
|
||||||
@@ -114,7 +112,7 @@ class UserAssetUrlActivity : BaseActivity() {
|
|||||||
MmkvManager.removeAssetUrl(editAssetId)
|
MmkvManager.removeAssetUrl(editAssetId)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -139,10 +137,12 @@ class UserAssetUrlActivity : BaseActivity() {
|
|||||||
deleteServer()
|
deleteServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.save_config -> {
|
R.id.save_config -> {
|
||||||
saveServer()
|
saveServer()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,19 +12,16 @@ import com.google.gson.JsonSerializer
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_HTTP
|
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_HTTPS
|
|
||||||
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.R
|
||||||
import com.v2ray.ang.dto.*
|
import com.v2ray.ang.dto.*
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
|
||||||
import com.v2ray.ang.extension.idnHost
|
|
||||||
import com.v2ray.ang.extension.removeWhiteSpace
|
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||||
|
import com.v2ray.ang.util.fmt.ShadowsocksFmt
|
||||||
|
import com.v2ray.ang.util.fmt.SocksFmt
|
||||||
|
import com.v2ray.ang.util.fmt.TrojanFmt
|
||||||
|
import com.v2ray.ang.util.fmt.VlessFmt
|
||||||
|
import com.v2ray.ang.util.fmt.VmessFmt
|
||||||
|
import com.v2ray.ang.util.fmt.WireguardFmt
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.net.URI
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object AngConfigManager {
|
object AngConfigManager {
|
||||||
@@ -205,9 +202,9 @@ object AngConfigManager {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* import config form qrcode or...
|
* parse config form qrcode or...
|
||||||
*/
|
*/
|
||||||
private fun importConfig(
|
private fun parseConfig(
|
||||||
str: String?,
|
str: String?,
|
||||||
subid: String,
|
subid: String,
|
||||||
removedSelectedServer: ServerConfig?
|
removedSelectedServer: ServerConfig?
|
||||||
@@ -217,254 +214,22 @@ object AngConfigManager {
|
|||||||
return R.string.toast_none_data
|
return R.string.toast_none_data
|
||||||
}
|
}
|
||||||
|
|
||||||
//maybe sub
|
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
||||||
if (TextUtils.isEmpty(subid) && (str.startsWith(PROTOCOL_HTTP) || str.startsWith(
|
VmessFmt.parseVmess(str)
|
||||||
PROTOCOL_HTTPS
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
MmkvManager.importUrlAsSubscription(str)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var config: ServerConfig? = null
|
|
||||||
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
|
||||||
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
|
||||||
config = ServerConfig.create(EConfigType.VMESS)
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
|
||||||
|
|
||||||
|
|
||||||
if (!tryParseNewVmess(str, config, allowInsecure)) {
|
|
||||||
if (str.indexOf("?") > 0) {
|
|
||||||
if (!tryResolveVmess4Kitsunebi(str, config)) {
|
|
||||||
return R.string.toast_incorrect_protocol
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
|
||||||
result = Utils.decode(result)
|
|
||||||
if (TextUtils.isEmpty(result)) {
|
|
||||||
return R.string.toast_decoding_failed
|
|
||||||
}
|
|
||||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
|
||||||
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.add)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
|
||||||
) {
|
|
||||||
return R.string.toast_incorrect_protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
config.remarks = vmessQRCode.ps
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = vmessQRCode.add
|
|
||||||
vnext.port = Utils.parseInt(vmessQRCode.port)
|
|
||||||
vnext.users[0].id = vmessQRCode.id
|
|
||||||
vnext.users[0].security =
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
|
|
||||||
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
|
||||||
}
|
|
||||||
val sni = streamSetting.populateTransportSettings(
|
|
||||||
vmessQRCode.net,
|
|
||||||
vmessQRCode.type,
|
|
||||||
vmessQRCode.host,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.host,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.type,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.host
|
|
||||||
)
|
|
||||||
|
|
||||||
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
vmessQRCode.tls, allowInsecure,
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
|
|
||||||
fingerprint, vmessQRCode.alpn, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||||
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
ShadowsocksFmt.parseShadowsocks(str)
|
||||||
if (!tryResolveResolveSip002(str, config)) {
|
|
||||||
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
|
||||||
val indexSplit = result.indexOf("#")
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
try {
|
|
||||||
config.remarks =
|
|
||||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
|
|
||||||
//part decode
|
|
||||||
val indexS = result.indexOf("@")
|
|
||||||
result = if (indexS > 0) {
|
|
||||||
Utils.decode(result.substring(0, indexS)) + result.substring(
|
|
||||||
indexS,
|
|
||||||
result.length
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Utils.decode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
|
|
||||||
val match = legacyPattern.matchEntire(result)
|
|
||||||
?: return R.string.toast_incorrect_protocol
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
|
||||||
server.port = match.groupValues[4].toInt()
|
|
||||||
server.password = match.groupValues[2]
|
|
||||||
server.method = match.groupValues[1].lowercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
||||||
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
SocksFmt.parseSocks(str)
|
||||||
val indexSplit = result.indexOf("#")
|
|
||||||
config = ServerConfig.create(EConfigType.SOCKS)
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
try {
|
|
||||||
config.remarks =
|
|
||||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
|
|
||||||
//part decode
|
|
||||||
val indexS = result.indexOf("@")
|
|
||||||
if (indexS > 0) {
|
|
||||||
result = Utils.decode(result.substring(0, indexS)) + result.substring(
|
|
||||||
indexS,
|
|
||||||
result.length
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
result = Utils.decode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
|
|
||||||
val match =
|
|
||||||
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
|
||||||
server.port = match.groupValues[4].toInt()
|
|
||||||
val socksUsersBean =
|
|
||||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
|
||||||
socksUsersBean.user = match.groupValues[1]
|
|
||||||
socksUsersBean.pass = match.groupValues[2]
|
|
||||||
server.users = listOf(socksUsersBean)
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
TrojanFmt.parseTrojan(str)
|
||||||
config = ServerConfig.create(EConfigType.TROJAN)
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
|
|
||||||
var flow = ""
|
|
||||||
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
|
|
||||||
if (uri.rawQuery != null) {
|
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
|
|
||||||
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
queryParam["type"] ?: "tcp",
|
|
||||||
queryParam["headerType"],
|
|
||||||
queryParam["host"],
|
|
||||||
queryParam["path"],
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["quicSecurity"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"]
|
|
||||||
)
|
|
||||||
fingerprint = queryParam["fp"] ?: ""
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
queryParam["security"] ?: TLS,
|
|
||||||
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
|
|
||||||
null, null, null
|
|
||||||
)
|
|
||||||
flow = queryParam["flow"] ?: ""
|
|
||||||
} else {
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
TLS, allowInsecure, "",
|
|
||||||
fingerprint, null, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = uri.idnHost
|
|
||||||
server.port = uri.port
|
|
||||||
server.password = uri.userInfo
|
|
||||||
server.flow = flow
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
VlessFmt.parseVless(str)
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
config = ServerConfig.create(EConfigType.VLESS)
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = uri.idnHost
|
|
||||||
vnext.port = uri.port
|
|
||||||
vnext.users[0].id = uri.userInfo
|
|
||||||
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
|
||||||
vnext.users[0].flow = queryParam["flow"] ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
val sni = streamSetting.populateTransportSettings(
|
|
||||||
queryParam["type"] ?: "tcp",
|
|
||||||
queryParam["headerType"],
|
|
||||||
queryParam["host"],
|
|
||||||
queryParam["path"],
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["quicSecurity"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"]
|
|
||||||
)
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
queryParam["security"] ?: "",
|
|
||||||
allowInsecure,
|
|
||||||
queryParam["sni"] ?: sni,
|
|
||||||
queryParam["fp"] ?: "",
|
|
||||||
queryParam["alpn"],
|
|
||||||
queryParam["pbk"] ?: "",
|
|
||||||
queryParam["sid"] ?: "",
|
|
||||||
queryParam["spx"] ?: ""
|
|
||||||
)
|
|
||||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
WireguardFmt.parseWireguard(str)
|
||||||
config = ServerConfig.create(EConfigType.WIREGUARD)
|
} else {
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
null
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
if (config == null) {
|
||||||
return R.string.toast_incorrect_protocol
|
return R.string.toast_incorrect_protocol
|
||||||
}
|
}
|
||||||
@@ -487,383 +252,21 @@ object AngConfigManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryParseNewVmess(
|
|
||||||
uriString: String,
|
|
||||||
config: ServerConfig,
|
|
||||||
allowInsecure: Boolean
|
|
||||||
): Boolean {
|
|
||||||
return runCatching {
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(uriString))
|
|
||||||
check(uri.scheme == "vmess")
|
|
||||||
val (_, protocol, tlsStr, uuid, alterId) =
|
|
||||||
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
|
|
||||||
.matchEntire(uri.userInfo)?.groupValues
|
|
||||||
?: error("parse user info fail.")
|
|
||||||
val tls = tlsStr.isNotBlank()
|
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = uri.idnHost
|
|
||||||
vnext.port = uri.port
|
|
||||||
vnext.users[0].id = uuid
|
|
||||||
vnext.users[0].security = DEFAULT_SECURITY
|
|
||||||
vnext.users[0].alterId = alterId.toInt()
|
|
||||||
}
|
|
||||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
|
||||||
val sni = streamSetting.populateTransportSettings(protocol,
|
|
||||||
queryParam["type"],
|
|
||||||
queryParam["host"]?.split("|")?.get(0) ?: "",
|
|
||||||
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["security"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"])
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
|
|
||||||
null, null, null
|
|
||||||
)
|
|
||||||
true
|
|
||||||
}.getOrElse { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
|
|
||||||
|
|
||||||
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
|
|
||||||
val indexSplit = result.indexOf("?")
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
result = Utils.decode(result)
|
|
||||||
|
|
||||||
val arr1 = result.split('@')
|
|
||||||
if (arr1.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val arr21 = arr1[0].split(':')
|
|
||||||
val arr22 = arr1[1].split(':')
|
|
||||||
if (arr21.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
config.remarks = "Alien"
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = arr22[0]
|
|
||||||
vnext.port = Utils.parseInt(arr22[1])
|
|
||||||
vnext.users[0].id = arr21[1]
|
|
||||||
vnext.users[0].security = arr21[0]
|
|
||||||
vnext.users[0].alterId = 0
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
|
||||||
try {
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
|
|
||||||
val method: String
|
|
||||||
val password: String
|
|
||||||
if (uri.userInfo.contains(":")) {
|
|
||||||
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
|
|
||||||
if (arrUserInfo.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
method = arrUserInfo[0]
|
|
||||||
password = Utils.urlDecode(arrUserInfo[1])
|
|
||||||
} else {
|
|
||||||
val base64Decode = Utils.decode(uri.userInfo)
|
|
||||||
val arrUserInfo = base64Decode.split(":").map { it.trim() }
|
|
||||||
if (arrUserInfo.count() < 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
method = arrUserInfo[0]
|
|
||||||
password = base64Decode.substringAfter(":")
|
|
||||||
}
|
|
||||||
|
|
||||||
val query = Utils.urlDecode(uri.query ?: "")
|
|
||||||
if (query != "") {
|
|
||||||
val queryPairs = HashMap<String, String>()
|
|
||||||
val pairs = query.split(";")
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
|
|
||||||
for (pair in pairs) {
|
|
||||||
val idx = pair.indexOf("=")
|
|
||||||
if (idx == -1) {
|
|
||||||
queryPairs[Utils.urlDecode(pair)] = "";
|
|
||||||
} else {
|
|
||||||
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
|
|
||||||
Utils.urlDecode(pair.substring(idx + 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
|
|
||||||
var sni: String? = ""
|
|
||||||
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
|
|
||||||
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
"tcp",
|
|
||||||
"http",
|
|
||||||
queryPairs["obfs-host"],
|
|
||||||
queryPairs["path"],
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} else if (queryPairs["plugin"] == "v2ray-plugin") {
|
|
||||||
var network = "ws";
|
|
||||||
if (queryPairs["mode"] == "quic") {
|
|
||||||
network = "quic";
|
|
||||||
}
|
|
||||||
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
network,
|
|
||||||
null,
|
|
||||||
queryPairs["host"],
|
|
||||||
queryPairs["path"],
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if ("tls" in queryPairs) {
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
"tls", false, sni ?: "", null, null, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = uri.idnHost
|
|
||||||
server.port = uri.port
|
|
||||||
server.password = password
|
|
||||||
server.method = method
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, e.toString())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* share config
|
* share config
|
||||||
*/
|
*/
|
||||||
private fun shareConfig(guid: String): String {
|
private fun shareConfig(guid: String): String {
|
||||||
try {
|
try {
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
|
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
|
||||||
val outbound = config.getProxyOutbound() ?: 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) {
|
return config.configType.protocolScheme + when (config.configType) {
|
||||||
EConfigType.VMESS -> {
|
EConfigType.VMESS -> VmessFmt.toUri(config)
|
||||||
val vmessQRCode = VmessQRCode()
|
|
||||||
vmessQRCode.v = "2"
|
|
||||||
vmessQRCode.ps = config.remarks
|
|
||||||
vmessQRCode.add = outbound.getServerAddress().orEmpty()
|
|
||||||
vmessQRCode.port = outbound.getServerPort().toString()
|
|
||||||
vmessQRCode.id = outbound.getPassword().orEmpty()
|
|
||||||
vmessQRCode.aid =
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
|
||||||
vmessQRCode.scy =
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
|
||||||
vmessQRCode.net = streamSetting.network
|
|
||||||
vmessQRCode.tls = streamSetting.security
|
|
||||||
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
|
||||||
vmessQRCode.alpn =
|
|
||||||
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
|
|
||||||
.orEmpty()
|
|
||||||
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
|
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
|
||||||
vmessQRCode.type = transportDetails[0]
|
|
||||||
vmessQRCode.host = transportDetails[1]
|
|
||||||
vmessQRCode.path = transportDetails[2]
|
|
||||||
}
|
|
||||||
val json = Gson().toJson(vmessQRCode)
|
|
||||||
Utils.encode(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.CUSTOM -> ""
|
EConfigType.CUSTOM -> ""
|
||||||
|
EConfigType.SHADOWSOCKS -> ShadowsocksFmt.toUri(config)
|
||||||
EConfigType.SHADOWSOCKS -> {
|
EConfigType.SOCKS -> SocksFmt.toUri(config)
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
EConfigType.VLESS -> VlessFmt.toUri(config)
|
||||||
val pw =
|
EConfigType.TROJAN -> TrojanFmt.toUri(config)
|
||||||
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
pw,
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + remark
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.SOCKS -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
val pw =
|
|
||||||
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",
|
|
||||||
Utils.encode(pw),
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + remark
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.VLESS,
|
|
||||||
EConfigType.TROJAN -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
|
|
||||||
val dicQuery = HashMap<String, String>()
|
|
||||||
if (config.configType == EConfigType.VLESS) {
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
|
||||||
if (!TextUtils.isEmpty(it)) {
|
|
||||||
dicQuery["flow"] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dicQuery["encryption"] =
|
|
||||||
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
|
||||||
else outbound.getSecurityEncryption().orEmpty()
|
|
||||||
} else if (config.configType == EConfigType.TROJAN) {
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
|
|
||||||
if (!TextUtils.isEmpty(it)) {
|
|
||||||
dicQuery["flow"] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
|
||||||
(streamSetting.tlsSettings
|
|
||||||
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
|
||||||
dicQuery["sni"] = tlsSetting.serverName
|
|
||||||
}
|
|
||||||
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
|
||||||
dicQuery["alpn"] =
|
|
||||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
|
||||||
dicQuery["fp"] = tlsSetting.fingerprint!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
|
||||||
dicQuery["pbk"] = tlsSetting.publicKey!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
|
||||||
dicQuery["sid"] = tlsSetting.shortId!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
|
||||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dicQuery["type"] =
|
|
||||||
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
|
||||||
|
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
|
||||||
when (streamSetting.network) {
|
|
||||||
"tcp" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"kcp" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"ws", "httpupgrade" -> {
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"http", "h2" -> {
|
|
||||||
dicQuery["type"] = "http"
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"quic" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
"grpc" -> {
|
|
||||||
dicQuery["mode"] = transportDetails[0]
|
|
||||||
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val query = "?" + dicQuery.toList().joinToString(
|
|
||||||
separator = "&",
|
|
||||||
transform = { it.first + "=" + it.second })
|
|
||||||
|
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
outbound.getPassword(),
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -983,7 +386,47 @@ object AngConfigManager {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fun importBatchConfig(servers: String?, subid: String, append: Boolean): Int {
|
fun importBatchConfig(server: String?, subid: String, append: Boolean): Pair<Int, Int> {
|
||||||
|
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseBatchConfig(server, subid, append)
|
||||||
|
}
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseCustomConfigServer(server, subid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var countSub = parseBatchSubscription(server)
|
||||||
|
if (countSub <= 0) {
|
||||||
|
countSub = parseBatchSubscription(Utils.decode(server))
|
||||||
|
}
|
||||||
|
if (countSub > 0) {
|
||||||
|
updateConfigViaSubAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
return count to countSub
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseBatchSubscription(servers: String?): Int {
|
||||||
|
try {
|
||||||
|
if (servers == null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
servers.lines()
|
||||||
|
.forEach { str ->
|
||||||
|
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
|
||||||
|
count += MmkvManager.importUrlAsSubscription(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseBatchConfig(servers: String?, subid: String, append: Boolean): Int {
|
||||||
try {
|
try {
|
||||||
if (servers == null) {
|
if (servers == null) {
|
||||||
return 0
|
return 0
|
||||||
@@ -991,7 +434,7 @@ object AngConfigManager {
|
|||||||
val removedSelectedServer =
|
val removedSelectedServer =
|
||||||
if (!TextUtils.isEmpty(subid) && !append) {
|
if (!TextUtils.isEmpty(subid) && !append) {
|
||||||
MmkvManager.decodeServerConfig(
|
MmkvManager.decodeServerConfig(
|
||||||
mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: ""
|
mainStorage?.decodeString(KEY_SELECTED_SERVER).orEmpty()
|
||||||
)?.let {
|
)?.let {
|
||||||
if (it.subscriptionId == subid) {
|
if (it.subscriptionId == subid) {
|
||||||
return@let it
|
return@let it
|
||||||
@@ -1004,16 +447,12 @@ object AngConfigManager {
|
|||||||
if (!append) {
|
if (!append) {
|
||||||
MmkvManager.removeServerViaSubid(subid)
|
MmkvManager.removeServerViaSubid(subid)
|
||||||
}
|
}
|
||||||
// var servers = server
|
|
||||||
// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) {
|
|
||||||
// servers = server.replace("\n", "")
|
|
||||||
// }
|
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
servers.lines()
|
servers.lines()
|
||||||
.reversed()
|
.reversed()
|
||||||
.forEach {
|
.forEach {
|
||||||
val resId = importConfig(it, subid, removedSelectedServer)
|
val resId = parseConfig(it, subid, removedSelectedServer)
|
||||||
if (resId == 0) {
|
if (resId == 0) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@@ -1025,24 +464,7 @@ object AngConfigManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
|
fun parseCustomConfigServer(server: String?, subid: String): Int {
|
||||||
val subId = Utils.getUuid()
|
|
||||||
val subItem = SubscriptionItem()
|
|
||||||
|
|
||||||
|
|
||||||
subItem.remarks = remark
|
|
||||||
subItem.url = url
|
|
||||||
subItem.enabled = enabled
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendCustomConfigServer(server: String?, subid: String): Int {
|
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1069,9 +491,10 @@ object AngConfigManager {
|
|||||||
|
|
||||||
if (serverList.isNotEmpty()) {
|
if (serverList.isNotEmpty()) {
|
||||||
var count = 0
|
var count = 0
|
||||||
for (srv in serverList) {
|
for (srv in serverList.reversed()) {
|
||||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||||
config.fullConfig = Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
config.fullConfig =
|
||||||
|
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
||||||
config.remarks = config.fullConfig?.remarks
|
config.remarks = config.fullConfig?.remarks
|
||||||
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
||||||
.toString())
|
.toString())
|
||||||
@@ -1094,8 +517,83 @@ object AngConfigManager {
|
|||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
serverRawStorage?.encode(key, server)
|
serverRawStorage?.encode(key, server)
|
||||||
return 1
|
return 1
|
||||||
|
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
|
||||||
|
val config = WireguardFmt.parseWireguardConfFile(server)
|
||||||
|
?: return R.string.toast_incorrect_protocol
|
||||||
|
config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||||
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
|
serverRawStorage?.encode(key, server)
|
||||||
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateConfigViaSubAll(): Int {
|
||||||
|
var count = 0
|
||||||
|
try {
|
||||||
|
MmkvManager.decodeSubscriptions().forEach {
|
||||||
|
count += updateConfigViaSub(it)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateConfigViaSub(it: Pair<String, SubscriptionItem>): Int {
|
||||||
|
try {
|
||||||
|
if (TextUtils.isEmpty(it.first)
|
||||||
|
|| TextUtils.isEmpty(it.second.remarks)
|
||||||
|
|| TextUtils.isEmpty(it.second.url)
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (!it.second.enabled) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val url = Utils.idnToASCII(it.second.url)
|
||||||
|
if (!Utils.isValidUrl(url)) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, url)
|
||||||
|
var configText = try {
|
||||||
|
Utils.getUrlContentWithCustomUserAgent(url)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
if (configText.isEmpty()) {
|
||||||
|
configText = try {
|
||||||
|
val httpPort = Utils.parseInt(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||||
|
AppConfig.PORT_HTTP.toInt()
|
||||||
|
)
|
||||||
|
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (configText.isEmpty()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return parseConfigViaSub(configText, it.first, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseConfigViaSub(server: String?, subid: String, append: Boolean): Int {
|
||||||
|
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseBatchConfig(server, subid, append)
|
||||||
|
}
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseCustomConfigServer(server, subid)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import android.content.pm.ApplicationInfo
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
import rx.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object AppManagerUtil {
|
object AppManagerUtil {
|
||||||
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
||||||
@@ -31,7 +30,8 @@ object AppManagerUtil {
|
|||||||
return apps
|
return apps
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
|
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> =
|
||||||
|
Observable.unsafeCreate {
|
||||||
it.onNext(loadNetworkAppList(ctx))
|
it.onNext(loadNetworkAppList(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.util
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.dto.AssetUrlItem
|
import com.v2ray.ang.dto.AssetUrlItem
|
||||||
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||||
import com.v2ray.ang.dto.ServerConfig
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
@@ -11,6 +12,7 @@ import java.net.URI
|
|||||||
object MmkvManager {
|
object MmkvManager {
|
||||||
const val ID_MAIN = "MAIN"
|
const val ID_MAIN = "MAIN"
|
||||||
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
||||||
|
const val ID_PROFILE_CONFIG = "PROFILE_CONFIG"
|
||||||
const val ID_SERVER_RAW = "SERVER_RAW"
|
const val ID_SERVER_RAW = "SERVER_RAW"
|
||||||
const val ID_SERVER_AFF = "SERVER_AFF"
|
const val ID_SERVER_AFF = "SERVER_AFF"
|
||||||
const val ID_SUB = "SUB"
|
const val ID_SUB = "SUB"
|
||||||
@@ -19,11 +21,14 @@ object MmkvManager {
|
|||||||
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||||
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||||
|
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
val settingsStorage by lazy { MMKV.mmkvWithID(ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val profileStorage by lazy { MMKV.mmkvWithID(ID_PROFILE_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
fun decodeServerList(): MutableList<String> {
|
fun decodeServerList(): MutableList<String> {
|
||||||
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||||
@@ -45,6 +50,17 @@ object MmkvManager {
|
|||||||
return Gson().fromJson(json, ServerConfig::class.java)
|
return Gson().fromJson(json, ServerConfig::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun decodeProfileConfig(guid: String): ProfileItem? {
|
||||||
|
if (guid.isBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val json = profileStorage?.decodeString(guid)
|
||||||
|
if (json.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Gson().fromJson(json, ProfileItem::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||||
val key = guid.ifBlank { Utils.getUuid() }
|
val key = guid.ifBlank { Utils.getUuid() }
|
||||||
serverStorage?.encode(key, Gson().toJson(config))
|
serverStorage?.encode(key, Gson().toJson(config))
|
||||||
@@ -56,6 +72,14 @@ object MmkvManager {
|
|||||||
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val profile = ProfileItem(
|
||||||
|
configType = config.configType,
|
||||||
|
subscriptionId = config.subscriptionId,
|
||||||
|
remarks = config.remarks,
|
||||||
|
server = config.getProxyOutbound()?.getServerAddress(),
|
||||||
|
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||||
|
)
|
||||||
|
profileStorage?.encode(key, Gson().toJson(profile))
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +94,7 @@ object MmkvManager {
|
|||||||
serverList.remove(guid)
|
serverList.remove(guid)
|
||||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
serverStorage?.remove(guid)
|
serverStorage?.remove(guid)
|
||||||
|
profileStorage?.remove(guid)
|
||||||
serverAffStorage?.remove(guid)
|
serverAffStorage?.remove(guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +131,8 @@ object MmkvManager {
|
|||||||
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearAllTestDelayResults() {
|
fun clearAllTestDelayResults(keys: List<String>?) {
|
||||||
serverAffStorage?.allKeys()?.forEach { key ->
|
keys?.forEach { key ->
|
||||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
aff.testDelayMillis = 0
|
aff.testDelayMillis = 0
|
||||||
serverAffStorage?.encode(key, Gson().toJson(aff))
|
serverAffStorage?.encode(key, Gson().toJson(aff))
|
||||||
@@ -124,7 +149,7 @@ object MmkvManager {
|
|||||||
}
|
}
|
||||||
val uri = URI(Utils.fixIllegalUrl(url))
|
val uri = URI(Utils.fixIllegalUrl(url))
|
||||||
val subItem = SubscriptionItem()
|
val subItem = SubscriptionItem()
|
||||||
subItem.remarks = Utils.urlDecode(uri.fragment ?: "import sub")
|
subItem.remarks = uri.fragment ?: "import sub"
|
||||||
subItem.url = url
|
subItem.url = url
|
||||||
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
|
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||||
return 1
|
return 1
|
||||||
@@ -138,8 +163,7 @@ object MmkvManager {
|
|||||||
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subscriptions.sortedBy { (_, value) -> value.addedTime }
|
return subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||||
return subscriptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeSubscription(subid: String) {
|
fun removeSubscription(subid: String) {
|
||||||
@@ -155,8 +179,7 @@ object MmkvManager {
|
|||||||
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||||
return assetUrlItems
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAssetUrl(assetid: String) {
|
fun removeAssetUrl(assetid: String) {
|
||||||
@@ -166,20 +189,29 @@ object MmkvManager {
|
|||||||
fun removeAllServer() {
|
fun removeAllServer() {
|
||||||
mainStorage?.clearAll()
|
mainStorage?.clearAll()
|
||||||
serverStorage?.clearAll()
|
serverStorage?.clearAll()
|
||||||
|
profileStorage?.clearAll()
|
||||||
serverAffStorage?.clearAll()
|
serverAffStorage?.clearAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeInvalidServer() {
|
fun removeInvalidServer(guid: String) {
|
||||||
|
if (guid.isNotEmpty()) {
|
||||||
|
decodeServerAffiliationInfo(guid)?.let { aff ->
|
||||||
|
if (aff.testDelayMillis < 0L) {
|
||||||
|
removeServer(guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
serverAffStorage?.allKeys()?.forEach { key ->
|
serverAffStorage?.allKeys()?.forEach { key ->
|
||||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
if (aff.testDelayMillis <= 0L) {
|
if (aff.testDelayMillis < 0L) {
|
||||||
removeServer(key)
|
removeServer(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun sortByTestResults( ) {
|
fun sortByTestResults() {
|
||||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||||
|
|
||||||
val serverDelays = mutableListOf<ServerDelay>()
|
val serverDelays = mutableListOf<ServerDelay>()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import android.content.res.Resources
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.LocaleList
|
import android.os.LocaleList
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
open class MyContextWrapper(base: Context?) : ContextWrapper(base) {
|
open class MyContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ package com.v2ray.ang.util
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import com.google.zxing.*
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.DecodeHintType
|
||||||
|
import com.google.zxing.EncodeHintType
|
||||||
|
import com.google.zxing.NotFoundException
|
||||||
|
import com.google.zxing.RGBLuminanceSource
|
||||||
import com.google.zxing.common.GlobalHistogramBinarizer
|
import com.google.zxing.common.GlobalHistogramBinarizer
|
||||||
import com.google.zxing.common.HybridBinarizer
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
import com.google.zxing.qrcode.QRCodeWriter
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
import java.util.*
|
import java.util.EnumMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述:解析二维码图片
|
* 描述:解析二维码图片
|
||||||
@@ -21,8 +26,10 @@ object QRCodeDecoder {
|
|||||||
try {
|
try {
|
||||||
val hints = HashMap<EncodeHintType, String>()
|
val hints = HashMap<EncodeHintType, String>()
|
||||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
val bitMatrix = QRCodeWriter().encode(text,
|
val bitMatrix = QRCodeWriter().encode(
|
||||||
BarcodeFormat.QR_CODE, size, size, hints)
|
text,
|
||||||
|
BarcodeFormat.QR_CODE, size, size, hints
|
||||||
|
)
|
||||||
val pixels = IntArray(size * size)
|
val pixels = IntArray(size * size)
|
||||||
for (y in 0 until size) {
|
for (y in 0 until size) {
|
||||||
for (x in 0 until size) {
|
for (x in 0 until size) {
|
||||||
@@ -34,8 +41,10 @@ object QRCodeDecoder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val bitmap = Bitmap.createBitmap(size, size,
|
val bitmap = Bitmap.createBitmap(
|
||||||
Bitmap.Config.ARGB_8888)
|
size, size,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||||
return bitmap
|
return bitmap
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -61,24 +70,37 @@ object QRCodeDecoder {
|
|||||||
* @return 返回二维码图片里的内容 或 null
|
* @return 返回二维码图片里的内容 或 null
|
||||||
*/
|
*/
|
||||||
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
|
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
var source: RGBLuminanceSource? = null
|
var source: RGBLuminanceSource? = null
|
||||||
try {
|
try {
|
||||||
val width = bitmap!!.width
|
val width = bitmap.width
|
||||||
val height = bitmap.height
|
val height = bitmap.height
|
||||||
val pixels = IntArray(width * height)
|
val pixels = IntArray(width * height)
|
||||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||||
source = RGBLuminanceSource(width, height, pixels)
|
source = RGBLuminanceSource(width, height, pixels)
|
||||||
return MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS).text
|
val qrReader = QRCodeReader()
|
||||||
|
try {
|
||||||
|
val result = try {
|
||||||
|
qrReader.decode(
|
||||||
|
BinaryBitmap(GlobalHistogramBinarizer(source)),
|
||||||
|
mapOf(DecodeHintType.TRY_HARDER to true)
|
||||||
|
)
|
||||||
|
} catch (e: NotFoundException) {
|
||||||
|
qrReader.decode(
|
||||||
|
BinaryBitmap(GlobalHistogramBinarizer(source.invert())),
|
||||||
|
mapOf(DecodeHintType.TRY_HARDER to true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result.text
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
if (source != null) {
|
} catch (e: Exception) {
|
||||||
try {
|
e.printStackTrace()
|
||||||
return MultiFormatReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS).text
|
|
||||||
} catch (e2: Throwable) {
|
|
||||||
e2.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,23 +129,24 @@ object QRCodeDecoder {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val allFormats: List<BarcodeFormat> = arrayListOf(
|
val allFormats: List<BarcodeFormat> = arrayListOf(
|
||||||
BarcodeFormat.AZTEC
|
BarcodeFormat.AZTEC,
|
||||||
,BarcodeFormat.CODABAR
|
BarcodeFormat.CODABAR,
|
||||||
,BarcodeFormat.CODE_39
|
BarcodeFormat.CODE_39,
|
||||||
,BarcodeFormat.CODE_93
|
BarcodeFormat.CODE_93,
|
||||||
,BarcodeFormat.CODE_128
|
BarcodeFormat.CODE_128,
|
||||||
,BarcodeFormat.DATA_MATRIX
|
BarcodeFormat.DATA_MATRIX,
|
||||||
,BarcodeFormat.EAN_8
|
BarcodeFormat.EAN_8,
|
||||||
,BarcodeFormat.EAN_13
|
BarcodeFormat.EAN_13,
|
||||||
,BarcodeFormat.ITF
|
BarcodeFormat.ITF,
|
||||||
,BarcodeFormat.MAXICODE
|
BarcodeFormat.MAXICODE,
|
||||||
,BarcodeFormat.PDF_417
|
BarcodeFormat.PDF_417,
|
||||||
,BarcodeFormat.QR_CODE
|
BarcodeFormat.QR_CODE,
|
||||||
,BarcodeFormat.RSS_14
|
BarcodeFormat.RSS_14,
|
||||||
,BarcodeFormat.RSS_EXPANDED
|
BarcodeFormat.RSS_EXPANDED,
|
||||||
,BarcodeFormat.UPC_A
|
BarcodeFormat.UPC_A,
|
||||||
,BarcodeFormat.UPC_E
|
BarcodeFormat.UPC_E,
|
||||||
,BarcodeFormat.UPC_EAN_EXTENSION)
|
BarcodeFormat.UPC_EAN_EXTENSION
|
||||||
|
)
|
||||||
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
||||||
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
||||||
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
|
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ import com.v2ray.ang.extension.responseLength
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.*
|
import java.net.HttpURLConnection
|
||||||
import java.util.*
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.net.Socket
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.UnknownHostException
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
object SpeedtestUtil {
|
object SpeedtestUtil {
|
||||||
@@ -34,7 +38,7 @@ object SpeedtestUtil {
|
|||||||
|
|
||||||
fun realPing(config: String): Long {
|
fun realPing(config: String): Long {
|
||||||
return try {
|
return try {
|
||||||
Libv2ray.measureOutboundDelay(config)
|
Libv2ray.measureOutboundDelay(config, Utils.getDelayTestUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
||||||
-1L
|
-1L
|
||||||
@@ -48,7 +52,8 @@ object SpeedtestUtil {
|
|||||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||||
if (!TextUtils.isEmpty(allText)) {
|
if (!TextUtils.isEmpty(allText)) {
|
||||||
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
||||||
val temps = tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
val temps =
|
||||||
|
tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
if (temps.count() > 0 && temps[0].length < 10) {
|
if (temps.count() > 0 && temps[0].length < 10) {
|
||||||
return temps[0].toFloat().toInt().toString() + "ms"
|
return temps[0].toFloat().toInt().toString() + "ms"
|
||||||
}
|
}
|
||||||
@@ -66,7 +71,7 @@ object SpeedtestUtil {
|
|||||||
tcpTestingSockets.add(socket)
|
tcpTestingSockets.add(socket)
|
||||||
}
|
}
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
socket.connect(InetSocketAddress(url, port),3000)
|
socket.connect(InetSocketAddress(url, port), 3000)
|
||||||
val time = System.currentTimeMillis() - start
|
val time = System.currentTimeMillis() - start
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
tcpTestingSockets.remove(socket)
|
tcpTestingSockets.remove(socket)
|
||||||
@@ -98,13 +103,14 @@ object SpeedtestUtil {
|
|||||||
var conn: HttpURLConnection? = null
|
var conn: HttpURLConnection? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val url = URL("https",
|
val url = URL(Utils.getDelayTestUrl())
|
||||||
"www.google.com",
|
|
||||||
"/generate_204")
|
|
||||||
|
|
||||||
conn = url.openConnection(
|
conn = url.openConnection(
|
||||||
Proxy(Proxy.Type.HTTP,
|
Proxy(
|
||||||
InetSocketAddress("127.0.0.1", port))) as HttpURLConnection
|
Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress("127.0.0.1", port)
|
||||||
|
)
|
||||||
|
) as HttpURLConnection
|
||||||
conn.connectTimeout = 30000
|
conn.connectTimeout = 30000
|
||||||
conn.readTimeout = 30000
|
conn.readTimeout = 30000
|
||||||
conn.setRequestProperty("Connection", "close")
|
conn.setRequestProperty("Connection", "close")
|
||||||
@@ -118,11 +124,19 @@ object SpeedtestUtil {
|
|||||||
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
||||||
result = context.getString(R.string.connection_test_available, elapsed)
|
result = context.getString(R.string.connection_test_available, elapsed)
|
||||||
} else {
|
} else {
|
||||||
throw IOException(context.getString(R.string.connection_test_error_status_code, code))
|
throw IOException(
|
||||||
|
context.getString(
|
||||||
|
R.string.connection_test_error_status_code,
|
||||||
|
code
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// network exception
|
// network exception
|
||||||
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
|
Log.d(
|
||||||
|
AppConfig.ANG_PACKAGE,
|
||||||
|
"testConnection IOException: " + Log.getStackTraceString(e)
|
||||||
|
)
|
||||||
result = context.getString(R.string.connection_test_error, e.message)
|
result = context.getString(R.string.connection_test_error, e.message)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// library exception, eg sumsung
|
// library exception, eg sumsung
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ object Utils {
|
|||||||
* @param text
|
* @param text
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun getEditable(text: String): Editable {
|
fun getEditable(text: String?): Editable {
|
||||||
return Editable.Factory.getInstance().newEditable(text)
|
return Editable.Factory.getInstance().newEditable(text.orEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,15 +63,10 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun parseInt(str: String?, default: Int): Int {
|
fun parseInt(str: String?, default: Int): Int {
|
||||||
str ?: return default
|
return str?.toIntOrNull() ?: default
|
||||||
return try {
|
|
||||||
Integer.parseInt(str)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get text from clipboard
|
* get text from clipboard
|
||||||
*/
|
*/
|
||||||
@@ -101,23 +96,19 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* base64 decode
|
* base64 decode
|
||||||
*/
|
*/
|
||||||
fun decode(text: String): String {
|
fun decode(text: String?): String {
|
||||||
tryDecodeBase64(text)?.let { return it }
|
return tryDecodeBase64(text) ?: text?.trimEnd('=')?.let { tryDecodeBase64(it) }.orEmpty()
|
||||||
if (text.endsWith('=')) {
|
|
||||||
// try again for some loosely formatted base64
|
|
||||||
tryDecodeBase64(text.trimEnd('='))?.let { return it }
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tryDecodeBase64(text: String): String? {
|
|
||||||
|
fun tryDecodeBase64(text: String?): String? {
|
||||||
try {
|
try {
|
||||||
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
return Base64.decode(text, Base64.NO_WRAP).toString(Charsets.UTF_8)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(ANG_PACKAGE, "Parse base64 standard failed $e")
|
Log.i(ANG_PACKAGE, "Parse base64 standard failed $e")
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
|
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(Charsets.UTF_8)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e")
|
Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e")
|
||||||
}
|
}
|
||||||
@@ -129,7 +120,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun encode(text: String): String {
|
fun encode(text: String): String {
|
||||||
return try {
|
return try {
|
||||||
Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
""
|
""
|
||||||
@@ -149,7 +140,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getVpnDnsServers(): List<String> {
|
fun getVpnDnsServers(): List<String> {
|
||||||
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)?:AppConfig.DNS_VPN
|
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS) ?: AppConfig.DNS_VPN
|
||||||
return vpnDns.split(",").filter { isPureIpAddress(it) }
|
return vpnDns.split(",").filter { isPureIpAddress(it) }
|
||||||
// allow empty, in that case dns will use system default
|
// allow empty, in that case dns will use system default
|
||||||
}
|
}
|
||||||
@@ -159,7 +150,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun getDomesticDnsServers(): List<String> {
|
fun getDomesticDnsServers(): List<String> {
|
||||||
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||||
val ret = domesticDns.split(",").filter { isPureIpAddress(it) }
|
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_DIRECT)
|
return listOf(AppConfig.DNS_DIRECT)
|
||||||
}
|
}
|
||||||
@@ -213,7 +204,8 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isIpv4Address(value: String): Boolean {
|
fun isIpv4Address(value: String): Boolean {
|
||||||
val regV4 = Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
val regV4 =
|
||||||
|
Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||||
return regV4.matches(value)
|
return regV4.matches(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,12 +215,13 @@ object Utils {
|
|||||||
addr = addr.drop(1)
|
addr = addr.drop(1)
|
||||||
addr = addr.dropLast(addr.count() - addr.lastIndexOf("]"))
|
addr = addr.dropLast(addr.count() - addr.lastIndexOf("]"))
|
||||||
}
|
}
|
||||||
val regV6 = Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
val regV6 =
|
||||||
|
Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||||
return regV6.matches(addr)
|
return regV6.matches(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isCoreDNSAddress(s: String): Boolean {
|
private fun isCoreDNSAddress(s: String): Boolean {
|
||||||
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic")
|
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic") || s == "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,7 +229,13 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun isValidUrl(value: String?): Boolean {
|
fun isValidUrl(value: String?): Boolean {
|
||||||
try {
|
try {
|
||||||
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
if (value.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (Patterns.WEB_URL.matcher(value).matches()
|
||||||
|
|| Patterns.DOMAIN_NAME.matcher(value).matches()
|
||||||
|
|| URLUtil.isValidUrl(value)
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -282,7 +281,7 @@ object Utils {
|
|||||||
|
|
||||||
fun urlDecode(url: String): String {
|
fun urlDecode(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
URLDecoder.decode(url, "UTF-8")
|
URLDecoder.decode(url, Charsets.UTF_8.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
url
|
url
|
||||||
@@ -291,7 +290,7 @@ object Utils {
|
|||||||
|
|
||||||
fun urlEncode(url: String): String {
|
fun urlEncode(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
URLEncoder.encode(url, "UTF-8")
|
URLEncoder.encode(url, Charsets.UTF_8.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
url
|
url
|
||||||
@@ -302,7 +301,10 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* readTextFromAssets
|
* readTextFromAssets
|
||||||
*/
|
*/
|
||||||
fun readTextFromAssets(context: Context, fileName: String): String {
|
fun readTextFromAssets(context: Context?, fileName: String): String {
|
||||||
|
if (context == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
val content = context.assets.open(fileName).bufferedReader().use {
|
val content = context.assets.open(fileName).bufferedReader().use {
|
||||||
it.readText()
|
it.readText()
|
||||||
}
|
}
|
||||||
@@ -326,7 +328,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDeviceIdForXUDPBaseKey(): String {
|
fun getDeviceIdForXUDPBaseKey(): String {
|
||||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8"))
|
val androidId = Settings.Secure.ANDROID_ID.toByteArray(Charsets.UTF_8)
|
||||||
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,14 +354,27 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getUrlContentWithCustomUserAgent(urlStr: String?): String {
|
fun getUrlContentWithCustomUserAgent(urlStr: String?, timeout: Int = 30000, httpPort: Int = 0): String {
|
||||||
val url = URL(urlStr)
|
val url = URL(urlStr)
|
||||||
val conn = url.openConnection()
|
val conn = if (httpPort == 0) {
|
||||||
|
url.openConnection()
|
||||||
|
} else {
|
||||||
|
url.openConnection(
|
||||||
|
Proxy(
|
||||||
|
Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress("127.0.0.1", httpPort)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
conn.connectTimeout = timeout
|
||||||
|
conn.readTimeout = timeout
|
||||||
conn.setRequestProperty("Connection", "close")
|
conn.setRequestProperty("Connection", "close")
|
||||||
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
||||||
url.userInfo?.let {
|
url.userInfo?.let {
|
||||||
conn.setRequestProperty("Authorization",
|
conn.setRequestProperty(
|
||||||
"Basic ${encode(urlDecode(it))}")
|
"Authorization",
|
||||||
|
"Basic ${encode(urlDecode(it))}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
conn.useCaches = false
|
conn.useCaches = false
|
||||||
return conn.inputStream.use {
|
return conn.inputStream.use {
|
||||||
@@ -368,10 +383,10 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDarkModeStatus(context: Context): Boolean {
|
fun getDarkModeStatus(context: Context): Boolean {
|
||||||
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
|
return context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK != UI_MODE_NIGHT_NO
|
||||||
return mode != UI_MODE_NIGHT_NO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun setNightMode(context: Context) {
|
fun setNightMode(context: Context) {
|
||||||
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
|
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
|
||||||
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
@@ -380,7 +395,10 @@ object Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIpv6Address(address: String): String {
|
fun getIpv6Address(address: String?): String {
|
||||||
|
if (address == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
||||||
String.format("[%s]", address)
|
String.format("[%s]", address)
|
||||||
} else {
|
} else {
|
||||||
@@ -388,17 +406,21 @@ object Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocale(context: Context): Locale =
|
fun getLocale(): Locale {
|
||||||
when (settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto") {
|
val lang = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto"
|
||||||
|
return when (lang) {
|
||||||
"auto" -> getSysLocale()
|
"auto" -> getSysLocale()
|
||||||
"en" -> Locale("en")
|
"en" -> Locale.ENGLISH
|
||||||
"zh-rCN" -> Locale("zh", "CN")
|
"zh-rCN" -> Locale.CHINA
|
||||||
"zh-rTW" -> Locale("zh", "TW")
|
"zh-rTW" -> Locale.TRADITIONAL_CHINESE
|
||||||
"vi" -> Locale("vi")
|
"vi" -> Locale("vi")
|
||||||
"ru" -> Locale("ru")
|
"ru" -> Locale("ru")
|
||||||
"fa" -> Locale("fa")
|
"fa" -> Locale("fa")
|
||||||
|
"bn" -> Locale("bn")
|
||||||
else -> getSysLocale()
|
else -> getSysLocale()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
LocaleList.getDefault()[0]
|
LocaleList.getDefault()[0]
|
||||||
@@ -408,8 +430,8 @@ object Utils {
|
|||||||
|
|
||||||
fun fixIllegalUrl(str: String): String {
|
fun fixIllegalUrl(str: String): String {
|
||||||
return str
|
return str
|
||||||
.replace(" ","%20")
|
.replace(" ", "%20")
|
||||||
.replace("|","%7C")
|
.replace("|", "%7C")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeWhiteSpace(str: String?): String? {
|
fun removeWhiteSpace(str: String?): String? {
|
||||||
@@ -425,5 +447,14 @@ object Utils {
|
|||||||
fun isTv(context: Context): Boolean =
|
fun isTv(context: Context): Boolean =
|
||||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
|
||||||
|
fun getDelayTestUrl(second: Boolean = false): String {
|
||||||
|
return if (second) {
|
||||||
|
AppConfig.DelayTestUrl2
|
||||||
|
} else {
|
||||||
|
settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,21 @@ package com.v2ray.ang.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import com.google.gson.*
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ERoutingMode
|
import com.v2ray.ang.dto.ERoutingMode
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||||
|
|
||||||
@@ -49,6 +53,14 @@ object V2rayConfigUtil {
|
|||||||
return Result(true, customConfig)
|
return Result(true, customConfig)
|
||||||
}
|
}
|
||||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||||
|
val address = outbound.getServerAddress() ?: return Result(false, "")
|
||||||
|
if (!Utils.isIpAddress(address)) {
|
||||||
|
if (!Utils.isValidUrl(address)) {
|
||||||
|
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
|
||||||
|
return Result(false, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
||||||
//Log.d(ANG_PACKAGE, result.content)
|
//Log.d(ANG_PACKAGE, result.content)
|
||||||
return result
|
return result
|
||||||
@@ -134,6 +146,8 @@ object V2rayConfigUtil {
|
|||||||
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||||
?: true
|
?: true
|
||||||
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
||||||
|
v2rayConfig.inbounds[0].sniffing?.routeOnly =
|
||||||
|
settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
|
||||||
if (!sniffAllTlsAndHttp) {
|
if (!sniffAllTlsAndHttp) {
|
||||||
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
||||||
}
|
}
|
||||||
@@ -158,13 +172,9 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
private fun fakedns(v2rayConfig: V2rayConfig) {
|
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|
||||||
|| settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
||||||
) {
|
) {
|
||||||
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
||||||
v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
|
|
||||||
.forEach {
|
|
||||||
it.settings?.domainStrategy = "UseIP"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,65 +183,82 @@ object V2rayConfigUtil {
|
|||||||
*/
|
*/
|
||||||
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||||
|
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||||
|
|
||||||
|
routingUserRule(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||||
|
.orEmpty(), TAG_BLOCKED, v2rayConfig
|
||||||
|
)
|
||||||
|
if (routingMode == ERoutingMode.GLOBAL_DIRECT.value) {
|
||||||
|
routingUserRule(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
|
.orEmpty(), TAG_DIRECT, v2rayConfig
|
||||||
|
)
|
||||||
routingUserRule(
|
routingUserRule(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
?: "", AppConfig.TAG_PROXY, v2rayConfig
|
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
routingUserRule(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
|
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||||
)
|
)
|
||||||
routingUserRule(
|
routingUserRule(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
?: "", AppConfig.TAG_DIRECT, v2rayConfig
|
.orEmpty(), TAG_DIRECT, v2rayConfig
|
||||||
)
|
|
||||||
routingUserRule(
|
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
|
||||||
?: "", AppConfig.TAG_BLOCKED, v2rayConfig
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
v2rayConfig.routing.domainStrategy =
|
v2rayConfig.routing.domainStrategy =
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||||
?: "IPIfNonMatch"
|
?: "IPIfNonMatch"
|
||||||
// v2rayConfig.routing.domainMatcher = "mph"
|
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
|
||||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
|
||||||
|
|
||||||
// Hardcode googleapis.cn
|
// Hardcode googleapis.cn gstatic.com
|
||||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_PROXY,
|
outboundTag = TAG_PROXY,
|
||||||
domain = arrayListOf("domain:googleapis.cn")
|
domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com")
|
||||||
)
|
)
|
||||||
|
|
||||||
when (routingMode) {
|
when (routingMode) {
|
||||||
ERoutingMode.BYPASS_LAN.value -> {
|
ERoutingMode.BYPASS_LAN.value -> {
|
||||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
ERoutingMode.BYPASS_MAINLAND.value -> {
|
ERoutingMode.BYPASS_MAINLAND.value -> {
|
||||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||||
routingGeo("domain", "geolocation-cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
|
||||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
|
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
|
||||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||||
routingGeo("domain", "geolocation-cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
|
||||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
ERoutingMode.GLOBAL_DIRECT.value -> {
|
ERoutingMode.GLOBAL_DIRECT.value -> {
|
||||||
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
|
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_DIRECT,
|
outboundTag = TAG_DIRECT,
|
||||||
port = "0-65535"
|
|
||||||
)
|
)
|
||||||
|
if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") {
|
||||||
|
globalDirect.port = "0-65535"
|
||||||
|
} else {
|
||||||
|
globalDirect.ip = arrayListOf("0.0.0.0/0", "::/0")
|
||||||
|
}
|
||||||
v2rayConfig.routing.rules.add(globalDirect)
|
v2rayConfig.routing.rules.add(globalDirect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(routingMode != ERoutingMode.GLOBAL_DIRECT.value) {
|
if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) {
|
||||||
v2rayConfig.routing.rules.add(
|
val globalProxy = V2rayConfig.RoutingBean.RulesBean(
|
||||||
V2rayConfig.RoutingBean.RulesBean(
|
outboundTag = TAG_PROXY,
|
||||||
outboundTag = AppConfig.TAG_PROXY,
|
)
|
||||||
port = "0-65535"
|
if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") {
|
||||||
))
|
globalProxy.port = "0-65535"
|
||||||
|
} else {
|
||||||
|
globalProxy.ip = arrayListOf("0.0.0.0/0", "::/0")
|
||||||
|
}
|
||||||
|
v2rayConfig.routing.rules.add(globalProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -286,22 +313,18 @@ object V2rayConfigUtil {
|
|||||||
rulesIP.ip = ArrayList()
|
rulesIP.ip = ArrayList()
|
||||||
|
|
||||||
userRule.split(",").map { it.trim() }.forEach {
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
if (it.startsWith("ext:") && it.contains("geoip")) {
|
||||||
rulesIP.ip?.add(it)
|
rulesIP.ip?.add(it)
|
||||||
} else if (it.isNotEmpty())
|
} else if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||||
// if (Utils.isValidUrl(it)
|
rulesIP.ip?.add(it)
|
||||||
// || it.startsWith("geosite:")
|
} else if (it.isNotEmpty()) {
|
||||||
// || it.startsWith("regexp:")
|
|
||||||
// || it.startsWith("domain:")
|
|
||||||
// || it.startsWith("full:"))
|
|
||||||
{
|
|
||||||
rulesDomain.domain?.add(it)
|
rulesDomain.domain?.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rulesDomain.domain?.size!! > 0) {
|
if ((rulesDomain.domain?.size ?: 0) > 0) {
|
||||||
v2rayConfig.routing.rules.add(rulesDomain)
|
v2rayConfig.routing.rules.add(rulesDomain)
|
||||||
}
|
}
|
||||||
if (rulesIP.ip?.size!! > 0) {
|
if ((rulesIP.ip?.size ?: 0) > 0) {
|
||||||
v2rayConfig.routing.rules.add(rulesIP)
|
v2rayConfig.routing.rules.add(rulesIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +333,7 @@ object V2rayConfigUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
private fun userRule2Domain(userRule: String): ArrayList<String> {
|
||||||
val domain = ArrayList<String>()
|
val domain = ArrayList<String>()
|
||||||
userRule.split(",").map { it.trim() }.forEach {
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||||
@@ -327,13 +350,13 @@ object V2rayConfigUtil {
|
|||||||
try {
|
try {
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||||
val geositeCn = arrayListOf("geosite:cn")
|
val geositeCn = arrayListOf("geosite:cn")
|
||||||
val proxyDomain = userRule2Domian(
|
val proxyDomain = userRule2Domain(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
?: ""
|
.orEmpty()
|
||||||
)
|
)
|
||||||
val directDomain = userRule2Domian(
|
val directDomain = userRule2Domain(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
?: ""
|
.orEmpty()
|
||||||
)
|
)
|
||||||
// fakedns with all domains to make it always top priority
|
// fakedns with all domains to make it always top priority
|
||||||
v2rayConfig.dns.servers?.add(
|
v2rayConfig.dns.servers?.add(
|
||||||
@@ -400,14 +423,15 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
private fun dns(v2rayConfig: V2rayConfig): Boolean {
|
private fun dns(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
val hosts = mutableMapOf<String, String>()
|
val hosts = mutableMapOf<String, Any>()
|
||||||
val servers = ArrayList<Any>()
|
val servers = ArrayList<Any>()
|
||||||
val remoteDns = Utils.getRemoteDnsServers()
|
|
||||||
val proxyDomain = userRule2Domian(
|
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
|
||||||
?: ""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
//remote Dns
|
||||||
|
val remoteDns = Utils.getRemoteDnsServers()
|
||||||
|
val proxyDomain = userRule2Domain(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
|
.orEmpty()
|
||||||
|
)
|
||||||
remoteDns.forEach {
|
remoteDns.forEach {
|
||||||
servers.add(it)
|
servers.add(it)
|
||||||
}
|
}
|
||||||
@@ -423,27 +447,29 @@ object V2rayConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// domestic DNS
|
// domestic DNS
|
||||||
val directDomain = userRule2Domian(
|
val domesticDns = Utils.getDomesticDnsServers()
|
||||||
|
val directDomain = userRule2Domain(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
?: ""
|
.orEmpty()
|
||||||
)
|
)
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||||
if (directDomain.size > 0 || routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
val isCnRoutingMode =
|
||||||
val domesticDns = Utils.getDomesticDnsServers()
|
(routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value)
|
||||||
val geositeCn = arrayListOf("geosite:cn","geosite:geolocation-cn")
|
|
||||||
val geoipCn = arrayListOf("geoip:cn")
|
val geoipCn = arrayListOf("geoip:cn")
|
||||||
|
|
||||||
if (directDomain.size > 0) {
|
if (directDomain.size > 0) {
|
||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
domesticDns.first(),
|
domesticDns.first(),
|
||||||
53,
|
53,
|
||||||
directDomain,
|
directDomain,
|
||||||
geoipCn
|
if (isCnRoutingMode) geoipCn else null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
if (isCnRoutingMode) {
|
||||||
|
val geositeCn = arrayListOf("geosite:cn")
|
||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
domesticDns.first(),
|
domesticDns.first(),
|
||||||
@@ -453,21 +479,22 @@ object V2rayConfigUtil {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.isPureIpAddress(domesticDns.first())) {
|
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||||
v2rayConfig.routing.rules.add(
|
v2rayConfig.routing.rules.add(
|
||||||
0, V2rayConfig.RoutingBean.RulesBean(
|
0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_DIRECT,
|
outboundTag = TAG_DIRECT,
|
||||||
port = "53",
|
port = "53",
|
||||||
ip = arrayListOf(domesticDns.first()),
|
ip = arrayListOf(domesticDns.first()),
|
||||||
domain = null
|
domain = null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val blkDomain = userRule2Domian(
|
//block dns
|
||||||
|
val blkDomain = userRule2Domain(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||||
?: ""
|
.orEmpty()
|
||||||
)
|
)
|
||||||
if (blkDomain.size > 0) {
|
if (blkDomain.size > 0) {
|
||||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||||
@@ -476,6 +503,12 @@ object V2rayConfigUtil {
|
|||||||
// hardcode googleapi rule to fix play store problems
|
// hardcode googleapi rule to fix play store problems
|
||||||
hosts["domain:googleapis.cn"] = "googleapis.com"
|
hosts["domain:googleapis.cn"] = "googleapis.com"
|
||||||
|
|
||||||
|
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||||
|
hosts["dns.pub"] = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||||
|
hosts["dns.alidns.com"] = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||||
|
hosts["one.one.one.one"] = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||||
|
hosts["dns.google"] = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||||
|
|
||||||
// DNS dns对象
|
// DNS dns对象
|
||||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||||
servers = servers,
|
servers = servers,
|
||||||
@@ -486,7 +519,7 @@ object V2rayConfigUtil {
|
|||||||
if (Utils.isPureIpAddress(remoteDns.first())) {
|
if (Utils.isPureIpAddress(remoteDns.first())) {
|
||||||
v2rayConfig.routing.rules.add(
|
v2rayConfig.routing.rules.add(
|
||||||
0, V2rayConfig.RoutingBean.RulesBean(
|
0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_PROXY,
|
outboundTag = TAG_PROXY,
|
||||||
port = "53",
|
port = "53",
|
||||||
ip = arrayListOf(remoteDns.first()),
|
ip = arrayListOf(remoteDns.first()),
|
||||||
domain = null
|
domain = null
|
||||||
@@ -559,7 +592,7 @@ object V2rayConfigUtil {
|
|||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
|
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -588,7 +621,8 @@ object V2rayConfigUtil {
|
|||||||
mux = null
|
mux = null
|
||||||
)
|
)
|
||||||
|
|
||||||
var packets = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
|
var packets =
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
|
||||||
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
|
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
|
||||||
&& packets == "tlshello"
|
&& packets == "tlshello"
|
||||||
) {
|
) {
|
||||||
@@ -606,7 +640,11 @@ object V2rayConfigUtil {
|
|||||||
?: "50-100",
|
?: "50-100",
|
||||||
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
|
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
|
||||||
?: "10-20"
|
?: "10-20"
|
||||||
)
|
),
|
||||||
|
noise = V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
|
||||||
|
packet = "rand:100-200",
|
||||||
|
delay = "10-20",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
fragmentOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean(
|
fragmentOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean(
|
||||||
sockopt = V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
|
sockopt = V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object ShadowsocksFmt {
|
||||||
|
fun parseShadowsocks(str: String): ServerConfig? {
|
||||||
|
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
||||||
|
if (!tryResolveResolveSip002(str, config)) {
|
||||||
|
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
||||||
|
val indexSplit = result.indexOf("#")
|
||||||
|
if (indexSplit > 0) {
|
||||||
|
try {
|
||||||
|
config.remarks =
|
||||||
|
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.substring(0, indexSplit)
|
||||||
|
}
|
||||||
|
|
||||||
|
//part decode
|
||||||
|
val indexS = result.indexOf("@")
|
||||||
|
result = if (indexS > 0) {
|
||||||
|
Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||||
|
indexS,
|
||||||
|
result.length
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Utils.decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
|
||||||
|
val match = legacyPattern.matchEntire(result)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||||
|
server.port = match.groupValues[4].toInt()
|
||||||
|
server.password = match.groupValues[2]
|
||||||
|
server.method = match.groupValues[1].lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val pw =
|
||||||
|
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
pw,
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + remark
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
||||||
|
try {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
|
|
||||||
|
val method: String
|
||||||
|
val password: String
|
||||||
|
if (uri.userInfo.contains(":")) {
|
||||||
|
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() != 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = Utils.urlDecode(arrUserInfo[1])
|
||||||
|
} else {
|
||||||
|
val base64Decode = Utils.decode(uri.userInfo)
|
||||||
|
val arrUserInfo = base64Decode.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() < 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = base64Decode.substringAfter(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
val query = Utils.urlDecode(uri.query.orEmpty())
|
||||||
|
if (query != "") {
|
||||||
|
val queryPairs = HashMap<String, String>()
|
||||||
|
val pairs = query.split(";")
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
|
||||||
|
for (pair in pairs) {
|
||||||
|
val idx = pair.indexOf("=")
|
||||||
|
if (idx == -1) {
|
||||||
|
queryPairs[Utils.urlDecode(pair)] = ""
|
||||||
|
} else {
|
||||||
|
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
|
||||||
|
Utils.urlDecode(pair.substring(idx + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
|
||||||
|
var sni: String? = ""
|
||||||
|
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
|
||||||
|
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
"tcp",
|
||||||
|
"http",
|
||||||
|
queryPairs["obfs-host"],
|
||||||
|
queryPairs["path"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else if (queryPairs["plugin"] == "v2ray-plugin") {
|
||||||
|
var network = "ws"
|
||||||
|
if (queryPairs["mode"] == "quic") {
|
||||||
|
network = "quic"
|
||||||
|
}
|
||||||
|
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
network,
|
||||||
|
null,
|
||||||
|
queryPairs["host"],
|
||||||
|
queryPairs["path"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ("tls" in queryPairs) {
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
"tls", false, sni.orEmpty(), null, null, null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = uri.idnHost
|
||||||
|
server.port = uri.port
|
||||||
|
server.password = password
|
||||||
|
server.method = method
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, e.toString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
|
object SocksFmt {
|
||||||
|
fun parseSocks(str: String): ServerConfig? {
|
||||||
|
val config = ServerConfig.create(EConfigType.SOCKS)
|
||||||
|
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
||||||
|
val indexSplit = result.indexOf("#")
|
||||||
|
|
||||||
|
if (indexSplit > 0) {
|
||||||
|
try {
|
||||||
|
config.remarks =
|
||||||
|
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.substring(0, indexSplit)
|
||||||
|
}
|
||||||
|
|
||||||
|
//part decode
|
||||||
|
val indexS = result.indexOf("@")
|
||||||
|
if (indexS > 0) {
|
||||||
|
result = Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||||
|
indexS,
|
||||||
|
result.length
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result = Utils.decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
|
||||||
|
val match =
|
||||||
|
legacyPattern.matchEntire(result) ?: return null
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||||
|
server.port = match.groupValues[4].toInt()
|
||||||
|
val socksUsersBean =
|
||||||
|
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
|
socksUsersBean.user = match.groupValues[1]
|
||||||
|
socksUsersBean.pass = match.groupValues[2]
|
||||||
|
server.users = listOf(socksUsersBean)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val pw =
|
||||||
|
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",
|
||||||
|
Utils.encode(pw),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
180
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/TrojanFmt.kt
Normal file
180
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/TrojanFmt.kt
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object TrojanFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING,
|
||||||
|
MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseTrojan(str: String): ServerConfig {
|
||||||
|
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.TROJAN)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
|
|
||||||
|
var flow = ""
|
||||||
|
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
|
||||||
|
if (uri.rawQuery.isNullOrEmpty()) {
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
V2rayConfig.TLS,
|
||||||
|
allowInsecure,
|
||||||
|
"",
|
||||||
|
fingerprint,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
queryParam["type"] ?: "tcp",
|
||||||
|
queryParam["headerType"],
|
||||||
|
queryParam["host"],
|
||||||
|
queryParam["path"],
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["quicSecurity"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"]
|
||||||
|
)
|
||||||
|
fingerprint = queryParam["fp"].orEmpty()
|
||||||
|
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
queryParam["security"] ?: V2rayConfig.TLS,
|
||||||
|
allowInsecure,
|
||||||
|
queryParam["sni"] ?: sni.orEmpty(),
|
||||||
|
fingerprint,
|
||||||
|
queryParam["alpn"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
flow = queryParam["flow"].orEmpty()
|
||||||
|
}
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = uri.idnHost
|
||||||
|
server.port = uri.port
|
||||||
|
server.password = uri.userInfo
|
||||||
|
server.flow = flow
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val dicQuery = HashMap<String, String>()
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
|
||||||
|
if (!TextUtils.isEmpty(it)) {
|
||||||
|
dicQuery["flow"] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||||
|
(streamSetting.tlsSettings
|
||||||
|
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||||
|
dicQuery["sni"] = tlsSetting.serverName
|
||||||
|
}
|
||||||
|
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||||
|
dicQuery["alpn"] =
|
||||||
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||||
|
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||||
|
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||||
|
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||||
|
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["type"] =
|
||||||
|
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||||
|
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
when (streamSetting.network) {
|
||||||
|
"tcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"kcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"ws", "httpupgrade", "splithttp" -> {
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"http", "h2" -> {
|
||||||
|
dicQuery["type"] = "http"
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"quic" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"grpc" -> {
|
||||||
|
dicQuery["mode"] = transportDetails[0]
|
||||||
|
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val query = "?" + dicQuery.toList().joinToString(
|
||||||
|
separator = "&",
|
||||||
|
transform = { it.first + "=" + it.second })
|
||||||
|
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
outbound.getPassword(),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
171
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VlessFmt.kt
Normal file
171
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VlessFmt.kt
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object VlessFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING,
|
||||||
|
MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVless(str: String): ServerConfig? {
|
||||||
|
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.VLESS)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||||
|
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
|
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = uri.idnHost
|
||||||
|
vnext.port = uri.port
|
||||||
|
vnext.users[0].id = uri.userInfo
|
||||||
|
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
||||||
|
vnext.users[0].flow = queryParam["flow"].orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val sni = streamSetting.populateTransportSettings(
|
||||||
|
queryParam["type"] ?: "tcp",
|
||||||
|
queryParam["headerType"],
|
||||||
|
queryParam["host"],
|
||||||
|
queryParam["path"],
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["quicSecurity"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"]
|
||||||
|
)
|
||||||
|
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
queryParam["security"].orEmpty(),
|
||||||
|
allowInsecure,
|
||||||
|
queryParam["sni"] ?: sni,
|
||||||
|
queryParam["fp"].orEmpty(),
|
||||||
|
queryParam["alpn"],
|
||||||
|
queryParam["pbk"].orEmpty(),
|
||||||
|
queryParam["sid"].orEmpty(),
|
||||||
|
queryParam["spx"].orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val dicQuery = HashMap<String, String>()
|
||||||
|
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
||||||
|
if (!TextUtils.isEmpty(it)) {
|
||||||
|
dicQuery["flow"] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["encryption"] =
|
||||||
|
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
||||||
|
else outbound.getSecurityEncryption().orEmpty()
|
||||||
|
|
||||||
|
|
||||||
|
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||||
|
(streamSetting.tlsSettings
|
||||||
|
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||||
|
dicQuery["sni"] = tlsSetting.serverName
|
||||||
|
}
|
||||||
|
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||||
|
dicQuery["alpn"] =
|
||||||
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||||
|
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||||
|
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||||
|
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||||
|
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["type"] =
|
||||||
|
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||||
|
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
when (streamSetting.network) {
|
||||||
|
"tcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"kcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"ws", "httpupgrade", "splithttp" -> {
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"http", "h2" -> {
|
||||||
|
dicQuery["type"] = "http"
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"quic" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"grpc" -> {
|
||||||
|
dicQuery["mode"] = transportDetails[0]
|
||||||
|
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val query = "?" + dicQuery.toList().joinToString(
|
||||||
|
separator = "&",
|
||||||
|
transform = { it.first + "=" + it.second })
|
||||||
|
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
outbound.getPassword(),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
160
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VmessFmt.kt
Normal file
160
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VmessFmt.kt
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.dto.VmessQRCode
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object VmessFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVmess(str: String): ServerConfig? {
|
||||||
|
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
|
||||||
|
return parseVmessStd(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.VMESS)
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||||
|
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
||||||
|
result = Utils.decode(result)
|
||||||
|
if (TextUtils.isEmpty(result)) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||||
|
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.add)
|
||||||
|
|| TextUtils.isEmpty(vmessQRCode.port)
|
||||||
|
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||||
|
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||||
|
) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
config.remarks = vmessQRCode.ps
|
||||||
|
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = vmessQRCode.add
|
||||||
|
vnext.port = Utils.parseInt(vmessQRCode.port)
|
||||||
|
vnext.users[0].id = vmessQRCode.id
|
||||||
|
vnext.users[0].security =
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.scy)) V2rayConfig.DEFAULT_SECURITY else vmessQRCode.scy
|
||||||
|
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
||||||
|
}
|
||||||
|
val sni = streamSetting.populateTransportSettings(
|
||||||
|
vmessQRCode.net,
|
||||||
|
vmessQRCode.type,
|
||||||
|
vmessQRCode.host,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.host,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.type,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.host
|
||||||
|
)
|
||||||
|
|
||||||
|
val fingerprint = vmessQRCode.fp
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
vmessQRCode.tls,
|
||||||
|
allowInsecure,
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
|
||||||
|
fingerprint,
|
||||||
|
vmessQRCode.alpn,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val vmessQRCode = VmessQRCode()
|
||||||
|
vmessQRCode.v = "2"
|
||||||
|
vmessQRCode.ps = config.remarks
|
||||||
|
vmessQRCode.add = outbound.getServerAddress().orEmpty()
|
||||||
|
vmessQRCode.port = outbound.getServerPort().toString()
|
||||||
|
vmessQRCode.id = outbound.getPassword().orEmpty()
|
||||||
|
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
||||||
|
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
||||||
|
vmessQRCode.net = streamSetting.network
|
||||||
|
vmessQRCode.tls = streamSetting.security
|
||||||
|
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
||||||
|
vmessQRCode.alpn =
|
||||||
|
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
|
||||||
|
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
vmessQRCode.type = transportDetails[0]
|
||||||
|
vmessQRCode.host = transportDetails[1]
|
||||||
|
vmessQRCode.path = transportDetails[2]
|
||||||
|
}
|
||||||
|
val json = Gson().toJson(vmessQRCode)
|
||||||
|
return Utils.encode(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVmessStd(str: String): ServerConfig? {
|
||||||
|
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.VMESS)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||||
|
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
|
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = uri.idnHost
|
||||||
|
vnext.port = uri.port
|
||||||
|
vnext.users[0].id = uri.userInfo
|
||||||
|
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
|
||||||
|
vnext.users[0].alterId = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val sni = streamSetting.populateTransportSettings(
|
||||||
|
queryParam["type"] ?: "tcp",
|
||||||
|
queryParam["headerType"],
|
||||||
|
queryParam["host"],
|
||||||
|
queryParam["path"],
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["quicSecurity"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"]
|
||||||
|
)
|
||||||
|
|
||||||
|
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
queryParam["security"].orEmpty(),
|
||||||
|
allowInsecure,
|
||||||
|
queryParam["sni"] ?: sni,
|
||||||
|
queryParam["fp"].orEmpty(),
|
||||||
|
queryParam["alpn"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.extension.removeWhiteSpace
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object WireguardFmt {
|
||||||
|
fun parseWireguard(str: String): ServerConfig? {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.rawQuery != null) {
|
||||||
|
val config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||||
|
|
||||||
|
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"]
|
||||||
|
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
|
||||||
|
.split(",")
|
||||||
|
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"].orEmpty()
|
||||||
|
wireguard.peers?.get(0)?.endpoint =
|
||||||
|
Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
|
||||||
|
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
|
wireguard.reserved =
|
||||||
|
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
|
||||||
|
.map { it.toInt() }
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseWireguardConfFile(str: String): ServerConfig? {
|
||||||
|
val config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||||
|
val queryParam: MutableMap<String, String> = mutableMapOf()
|
||||||
|
|
||||||
|
var currentSection: String? = null
|
||||||
|
|
||||||
|
str.lines().forEach { line ->
|
||||||
|
val trimmedLine = line.trim()
|
||||||
|
|
||||||
|
when {
|
||||||
|
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
|
||||||
|
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
|
||||||
|
trimmedLine.isBlank() || trimmedLine.startsWith("#") -> Unit // Skip blank lines or comments
|
||||||
|
currentSection != null -> {
|
||||||
|
val (key, value) = trimmedLine.split("=").map { it.trim() }
|
||||||
|
queryParam[key.lowercase()] = value // Store the key in lowercase for case-insensitivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.let { wireguard ->
|
||||||
|
wireguard.secretKey = queryParam["privatekey"].orEmpty()
|
||||||
|
wireguard.address = (queryParam["address"] ?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace().split(",")
|
||||||
|
wireguard.peers?.getOrNull(0)?.publicKey = queryParam["publickey"].orEmpty()
|
||||||
|
wireguard.peers?.getOrNull(0)?.endpoint = queryParam["endpoint"].orEmpty()
|
||||||
|
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
|
wireguard.reserved = (queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",").map { it.toInt() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +1,56 @@
|
|||||||
package com.v2ray.ang.viewmodel
|
package com.v2ray.ang.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.*
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.res.AssetManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
|
||||||
import com.v2ray.ang.AngApplication
|
import com.v2ray.ang.AngApplication
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.DialogConfigFilterBinding
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.*
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.ServersCache
|
||||||
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.*
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
|
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
|
||||||
|
import com.v2ray.ang.util.MessageUtil
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
||||||
import kotlinx.coroutines.*
|
import com.v2ray.ang.util.MmkvManager.subStorage
|
||||||
import java.util.*
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import com.v2ray.ang.util.V2rayConfigUtil
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val mainStorage by lazy {
|
private var serverList = MmkvManager.decodeServerList()
|
||||||
MMKV.mmkvWithID(
|
var subscriptionId: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
|
||||||
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 keywordFilter: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
|
||||||
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")!!
|
var keywordFilter = ""
|
||||||
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")!!
|
|
||||||
private set
|
|
||||||
val serversCache = mutableListOf<ServersCache>()
|
val serversCache = mutableListOf<ServersCache>()
|
||||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||||
|
|
||||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||||
|
|
||||||
fun startListenBroadcast() {
|
fun startListenBroadcast() {
|
||||||
@@ -95,49 +93,108 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appendCustomConfigServer(server: String) {
|
fun appendCustomConfigServer(server: String): Boolean {
|
||||||
|
if (server.contains("inbounds")
|
||||||
|
&& server.contains("outbounds")
|
||||||
|
&& server.contains("routing")
|
||||||
|
) {
|
||||||
|
try {
|
||||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||||
config.subscriptionId = subscriptionId
|
config.subscriptionId = subscriptionId
|
||||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
serverRawStorage?.encode(key, server)
|
MmkvManager.serverRawStorage?.encode(key, server)
|
||||||
serverList.add(0, key)
|
serverList.add(0, key)
|
||||||
serversCache.add(0, ServersCache(key, config))
|
val profile = ProfileItem(
|
||||||
|
configType = config.configType,
|
||||||
|
subscriptionId = config.subscriptionId,
|
||||||
|
remarks = config.remarks,
|
||||||
|
server = config.getProxyOutbound()?.getServerAddress(),
|
||||||
|
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||||
|
)
|
||||||
|
serversCache.add(0, ServersCache(key, profile))
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||||
Collections.swap(serverList, fromPosition, toPosition)
|
Collections.swap(serverList, fromPosition, toPosition)
|
||||||
Collections.swap(serversCache, fromPosition, toPosition)
|
Collections.swap(serversCache, fromPosition, toPosition)
|
||||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
MmkvManager.mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun updateCache() {
|
fun updateCache() {
|
||||||
serversCache.clear()
|
serversCache.clear()
|
||||||
for (guid in serverList) {
|
for (guid in serverList) {
|
||||||
|
var profile = MmkvManager.decodeProfileConfig(guid)
|
||||||
|
if (profile == null) {
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
||||||
if (subscriptionId.isNotEmpty() && subscriptionId != config.subscriptionId) {
|
profile = ProfileItem(
|
||||||
|
configType = config.configType,
|
||||||
|
subscriptionId = config.subscriptionId,
|
||||||
|
remarks = config.remarks,
|
||||||
|
server = config.getProxyOutbound()?.getServerAddress(),
|
||||||
|
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||||
|
)
|
||||||
|
MmkvManager.encodeServerConfig(guid, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywordFilter.isEmpty() || config.remarks.contains(keywordFilter)) {
|
if (keywordFilter.isEmpty() || profile.remarks.contains(keywordFilter)) {
|
||||||
serversCache.add(ServersCache(guid, config))
|
serversCache.add(ServersCache(guid, profile))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateConfigViaSubAll(): Int {
|
||||||
|
if (subscriptionId.isNullOrEmpty()) {
|
||||||
|
return AngConfigManager.updateConfigViaSubAll()
|
||||||
|
} else {
|
||||||
|
val json = subStorage?.decodeString(subscriptionId)
|
||||||
|
if (!json.isNullOrBlank()) {
|
||||||
|
return updateConfigViaSub(Pair(subscriptionId, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportAllServer(): Int {
|
||||||
|
val serverListCopy =
|
||||||
|
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||||
|
serverList
|
||||||
|
} else {
|
||||||
|
serversCache.map { it.guid }.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val ret = AngConfigManager.shareNonCustomConfigsToClipboard(
|
||||||
|
getApplication<AngApplication>(),
|
||||||
|
serverListCopy
|
||||||
|
)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun testAllTcping() {
|
fun testAllTcping() {
|
||||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
SpeedtestUtil.closeAllTcpSockets()
|
SpeedtestUtil.closeAllTcpSockets()
|
||||||
MmkvManager.clearAllTestDelayResults()
|
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||||
updateListAction.value = -1 // update all
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||||
for (item in serversCache) {
|
for (item in serversCache) {
|
||||||
item.config.getProxyOutbound()?.let { outbound ->
|
item.profile.let { outbound ->
|
||||||
val serverAddress = outbound.getServerAddress()
|
val serverAddress = outbound.server
|
||||||
val serverPort = outbound.getServerPort()
|
val serverPort = outbound.serverPort
|
||||||
if (serverAddress != null && serverPort != null) {
|
if (serverAddress != null && serverPort != null) {
|
||||||
tcpingTestScope.launch {
|
tcpingTestScope.launch {
|
||||||
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
|
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
|
||||||
@@ -153,7 +210,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
fun testAllRealPing() {
|
fun testAllRealPing() {
|
||||||
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
|
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
|
||||||
MmkvManager.clearAllTestDelayResults()
|
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||||
updateListAction.value = -1 // update all
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
val serversCopy = serversCache.toList() // Create a copy of the list
|
val serversCopy = serversCache.toList() // Create a copy of the list
|
||||||
@@ -177,60 +234,30 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
|
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterConfig(context: Context) {
|
fun subscriptionIdChanged(id: String) {
|
||||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
if (subscriptionId != id) {
|
||||||
val listId = subscriptions.map { it.first }.toList().toMutableList()
|
subscriptionId = id
|
||||||
val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList()
|
MmkvManager.settingsStorage.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
|
||||||
listRemarks += context.getString(R.string.filter_config_all)
|
|
||||||
val checkedItem = if (subscriptionId.isNotEmpty()) {
|
|
||||||
listId.indexOf(subscriptionId)
|
|
||||||
} else {
|
|
||||||
listRemarks.count() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context))
|
|
||||||
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)
|
|
||||||
builder.setTitle(R.string.title_filter_config)
|
|
||||||
builder.setPositiveButton(R.string.tasker_setting_confirm) { dialogInterface: DialogInterface?, _: Int ->
|
|
||||||
try {
|
|
||||||
val position = ivBinding.spSubscriptionId.selectedItemPosition
|
|
||||||
subscriptionId = if (listRemarks.count() - 1 == position) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
subscriptions[position].first
|
|
||||||
}
|
|
||||||
keywordFilter = ivBinding.etKeyword.text.toString()
|
|
||||||
settingsStorage?.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
|
|
||||||
settingsStorage?.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
|
|
||||||
reloadServerList()
|
reloadServerList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dialogInterface?.dismiss()
|
fun getSubscriptions(context: Context): Pair<MutableList<String>?, MutableList<String>?> {
|
||||||
} catch (e: Exception) {
|
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||||
e.printStackTrace()
|
if (subscriptionId.isNotEmpty()
|
||||||
|
&& !subscriptions.map { it.first }.contains(subscriptionId)
|
||||||
|
) {
|
||||||
|
subscriptionIdChanged("")
|
||||||
}
|
}
|
||||||
|
if (subscriptions.isEmpty()) {
|
||||||
|
return null to null
|
||||||
}
|
}
|
||||||
builder.show()
|
val listId = subscriptions.map { it.first }.toMutableList()
|
||||||
// AlertDialog.Builder(context)
|
listId.add(0, "")
|
||||||
// .setSingleChoiceItems(listRemarks.toTypedArray(), checkedItem) { dialog, i ->
|
val listRemarks = subscriptions.map { it.second.remarks }.toMutableList()
|
||||||
// try {
|
listRemarks.add(0, context.getString(R.string.filter_config_all))
|
||||||
// subscriptionId = if (listRemarks.count() - 1 == i) {
|
|
||||||
// ""
|
return listId to listRemarks
|
||||||
// } else {
|
|
||||||
// subscriptions[i].first
|
|
||||||
// }
|
|
||||||
// reloadServerList()
|
|
||||||
// dialog.dismiss()
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// e.printStackTrace()
|
|
||||||
// }
|
|
||||||
// }.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPosition(guid: String): Int {
|
fun getPosition(guid: String): Int {
|
||||||
@@ -241,15 +268,21 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeDuplicateServer() {
|
fun removeDuplicateServer(): Int {
|
||||||
|
val serversCacheCopy = mutableListOf<Pair<String, ServerConfig>>()
|
||||||
|
for (it in serversCache) {
|
||||||
|
val config = MmkvManager.decodeServerConfig(it.guid) ?: continue
|
||||||
|
serversCacheCopy.add(Pair(it.guid, config))
|
||||||
|
}
|
||||||
|
|
||||||
val deleteServer = mutableListOf<String>()
|
val deleteServer = mutableListOf<String>()
|
||||||
serversCache.forEachIndexed { index, it ->
|
serversCacheCopy.forEachIndexed { index, it ->
|
||||||
val outbound = it.config.getProxyOutbound()
|
val outbound = it.second.getProxyOutbound()
|
||||||
serversCache.forEachIndexed { index2, it2 ->
|
serversCacheCopy.forEachIndexed { index2, it2 ->
|
||||||
if (index2 > index) {
|
if (index2 > index) {
|
||||||
val outbound2 = it2.config.getProxyOutbound()
|
val outbound2 = it2.second.getProxyOutbound()
|
||||||
if (outbound == outbound2 && !deleteServer.contains(it2.guid)) {
|
if (outbound == outbound2 && !deleteServer.contains(it2.first)) {
|
||||||
deleteServer.add(it2.guid)
|
deleteServer.add(it2.first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,13 +290,70 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
for (it in deleteServer) {
|
for (it in deleteServer) {
|
||||||
MmkvManager.removeServer(it)
|
MmkvManager.removeServer(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return deleteServer.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAllServer() {
|
||||||
|
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||||
|
MmkvManager.removeAllServer()
|
||||||
|
} else {
|
||||||
|
val serversCopy = serversCache.toList()
|
||||||
|
for (item in serversCopy) {
|
||||||
|
MmkvManager.removeServer(item.guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeInvalidServer() {
|
||||||
|
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||||
|
MmkvManager.removeInvalidServer("")
|
||||||
|
} else {
|
||||||
|
val serversCopy = serversCache.toList()
|
||||||
|
for (item in serversCopy) {
|
||||||
|
MmkvManager.removeInvalidServer(item.guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortByTestResults() {
|
||||||
|
MmkvManager.sortByTestResults()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun copyAssets(assets: AssetManager) {
|
||||||
|
val extFolder = Utils.userAssetPath(getApplication<AngApplication>())
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val geo = arrayOf("geosite.dat", "geoip.dat")
|
||||||
|
assets.list("")
|
||||||
|
?.filter { geo.contains(it) }
|
||||||
|
?.filter { !File(extFolder, it).exists() }
|
||||||
|
?.forEach {
|
||||||
|
val target = File(extFolder, it)
|
||||||
|
assets.open(it).use { input ->
|
||||||
|
FileOutputStream(target).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(
|
||||||
|
ANG_PACKAGE,
|
||||||
|
"Copied from apk assets folder to ${target.absolutePath}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterConfig(keyword: String) {
|
||||||
|
if (keyword == keywordFilter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keywordFilter = keyword
|
||||||
|
MmkvManager.settingsStorage.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
|
||||||
reloadServerList()
|
reloadServerList()
|
||||||
getApplication<AngApplication>().toast(
|
|
||||||
getApplication<AngApplication>().getString(
|
|
||||||
R.string.title_del_duplicate_config_count,
|
|
||||||
deleteServer.count()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||||
@@ -296,7 +386,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||||
val resultPair = intent.getSerializableExtra("content") as Pair<String, Long>
|
val resultPair: Pair<String, Long> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.getSerializableExtra("content", Pair::class.java) as Pair<String, Long>
|
||||||
|
} else {
|
||||||
|
intent.getSerializableExtra("content") as Pair<String, Long>
|
||||||
|
}
|
||||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||||
updateListAction.value = getPosition(resultPair.first)
|
updateListAction.value = getPosition(resultPair.first)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
AppConfig.PREF_VPN_DNS,
|
AppConfig.PREF_VPN_DNS,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
|
AppConfig.PREF_DELAY_TEST_URL,
|
||||||
AppConfig.PREF_LOCAL_DNS_PORT,
|
AppConfig.PREF_LOCAL_DNS_PORT,
|
||||||
AppConfig.PREF_SOCKS_PORT,
|
AppConfig.PREF_SOCKS_PORT,
|
||||||
AppConfig.PREF_HTTP_PORT,
|
AppConfig.PREF_HTTP_PORT,
|
||||||
@@ -59,6 +60,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||||
AppConfig.PREF_SPEED_ENABLED,
|
AppConfig.PREF_SPEED_ENABLED,
|
||||||
AppConfig.PREF_PROXY_SHARING,
|
AppConfig.PREF_PROXY_SHARING,
|
||||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:fromAlpha="0.0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:toAlpha="1.0" />
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:fromAlpha="1.0"
|
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
|
||||||
android:toAlpha="0.0" />
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 526 B |
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<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="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
|
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
|
android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
|
android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
|
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
|
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:tint="#FFFFFF"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:viewportHeight="24"
|
||||||
<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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
|
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
|
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
|
||||||
android:fillColor="#FFFFFFFF"/>
|
android:fillColor="#FFFFFFFF" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="#FFFFFFFF"
|
||||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<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.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
|
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
|
android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
|
android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
|
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
|
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:tint="#000000"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:viewportHeight="24"
|
||||||
<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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
|
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
|
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
|
||||||
android:fillColor="#FF000000"/>
|
android:fillColor="#FF000000" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
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"/>
|
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>
|
</vector>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
<vector android:height="24dp" android:viewportHeight="1024"
|
<vector android:height="24dp"
|
||||||
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:viewportHeight="1024"
|
||||||
<path android:fillColor="#FF000000" android:pathData="M469.2,802.1l-81.7,-24.6L554.8,221.9l81.7,24.6L469.2,802.1zM362.7,654.5l-124.7,-141.7 124.8,-143.5 -64.4,-56 -173.8,199.8 174,197.7 64.1,-56.3zM899.4,513.1l-173.8,-199.8 -64.4,56 124.8,143.5 -124.7,141.7 64.1,56.4 174,-197.7z"/>
|
android:viewportWidth="1024"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M469.2,802.1l-81.7,-24.6L554.8,221.9l81.7,24.6L469.2,802.1zM362.7,654.5l-124.7,-141.7 124.8,-143.5 -64.4,-56 -173.8,199.8 174,197.7 64.1,-56.3zM899.4,513.1l-173.8,-199.8 -64.4,56 124.8,143.5 -124.7,141.7 64.1,56.4 174,-197.7z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -17,7 +20,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -51,13 +54,37 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_share"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_configuration_share"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_restore"
|
android:id="@+id/layout_restore"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -89,7 +116,7 @@
|
|||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -113,7 +140,7 @@
|
|||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -138,7 +165,7 @@
|
|||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -162,7 +189,7 @@
|
|||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
@@ -197,6 +224,7 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,22 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/pb_waiting"
|
||||||
|
app:indicatorColor="@color/color_fab_active"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tab_group"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabTextAppearance="@style/TabLayoutTextStyle"
|
||||||
|
app:tabIndicatorFullWidth="false"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -63,12 +79,11 @@
|
|||||||
android:minLines="1"
|
android:minLines="1"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:text="@string/connection_test_pending"
|
android:text="@string/connection_test_pending"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fabProgressCircle"
|
android:id="@+id/fabProgressCircle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -105,7 +120,7 @@
|
|||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
app:headerLayout="@layout/nav_header"
|
app:headerLayout="@layout/nav_header"
|
||||||
app:itemIconTint="@color/colorAccent"
|
app:itemIconTint="@color/colorAccent"
|
||||||
app:menu="@menu/menu_drawer" >
|
app:menu="@menu/menu_drawer">
|
||||||
|
|
||||||
</com.google.android.material.navigation.NavigationView>
|
</com.google.android.material.navigation.NavigationView>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"></RelativeLayout>
|
||||||
</RelativeLayout>
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
|||||||
@@ -173,6 +173,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -192,6 +193,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user