Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7367baffb8 | ||
|
|
819ff2995a | ||
|
|
3b5d04b717 | ||
|
|
b673cd73ac | ||
|
|
649c1a022b | ||
|
|
034e58bc9d | ||
|
|
a95f280102 | ||
|
|
df8da05f32 | ||
|
|
635581719b | ||
|
|
77d5e203e8 | ||
|
|
370d002b25 | ||
|
|
18f0fe47ff | ||
|
|
cccd6139fc | ||
|
|
1fadca8524 | ||
|
|
af01e2ac06 | ||
|
|
de22e16cd4 | ||
|
|
e073b19343 | ||
|
|
a81a05cd45 | ||
|
|
fc67281d2a | ||
|
|
ece35b9a1c | ||
|
|
ed8fb7fa82 | ||
|
|
3688dd4634 | ||
|
|
7ff445ef55 | ||
|
|
e4847b4c76 | ||
|
|
5f5d8f1d2f | ||
|
|
a45aa489e8 | ||
|
|
9b86ba9e35 | ||
|
|
f5b74ecd78 | ||
|
|
2d0ab355d6 | ||
|
|
e41235f76b | ||
|
|
3045f7000e | ||
|
|
c0c2dfb657 | ||
|
|
622cafbfd6 | ||
|
|
d8a1f66af9 | ||
|
|
c34cce63b0 | ||
|
|
f8f17b5d38 | ||
|
|
1d3a194d89 | ||
|
|
056384fdab | ||
|
|
7c40d074f3 | ||
|
|
c61595eb0d | ||
|
|
7192c970fa | ||
|
|
cfa709c651 | ||
|
|
97c467af41 | ||
|
|
6039426bac | ||
|
|
862fb90de9 | ||
|
|
842c32f29f | ||
|
|
0501de1658 | ||
|
|
dbe26847c7 | ||
|
|
806290f0a5 | ||
|
|
325c643314 | ||
|
|
4adc0affbe | ||
|
|
b5026095a0 | ||
|
|
fa7da3be10 | ||
|
|
35b44f1955 | ||
|
|
4708ee8823 | ||
|
|
52471f2ace | ||
|
|
0b065c745d | ||
|
|
9ce8244065 | ||
|
|
b7fafa1bf9 | ||
|
|
114c974ce5 | ||
|
|
e035925d25 | ||
|
|
c0fda6fcba | ||
|
|
75c90e3c45 | ||
|
|
9960f49698 | ||
|
|
1a2c4cc9a1 | ||
|
|
ee4f05b07d | ||
|
|
17ef476ede | ||
|
|
141b98631c | ||
|
|
845562bca3 | ||
|
|
105a41eeea |
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 585
|
||||
versionName = "1.8.39"
|
||||
versionCode = 601
|
||||
versionName = "1.9.7"
|
||||
multiDexEnabled = true
|
||||
splits {
|
||||
abi {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:smallScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<uses-sdk
|
||||
@@ -44,13 +44,13 @@
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name=".AngApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppThemeDayNight"
|
||||
@@ -77,55 +77,57 @@
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ServerActivity"
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ServerCustomConfigActivity"
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.SettingsActivity" />
|
||||
android:name=".ui.SettingsActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.PerAppProxyActivity" />
|
||||
android:name=".ui.PerAppProxyActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScannerActivity" />
|
||||
android:name=".ui.ScannerActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.LogcatActivity" />
|
||||
android:name=".ui.LogcatActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.RoutingSettingsActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
android:name=".ui.RoutingSettingActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.SubSettingActivity" />
|
||||
android:name=".ui.RoutingEditActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.UserAssetActivity" />
|
||||
android:name=".ui.SubSettingActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.UserAssetUrlActivity" />
|
||||
android:name=".ui.UserAssetActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.UserAssetUrlActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.SubEditActivity" />
|
||||
android:name=".ui.SubEditActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScScannerActivity" />
|
||||
android:name=".ui.ScScannerActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScSwitchActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:process=":RunSoLibV2RayDaemon"
|
||||
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
||||
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".ui.UrlSchemeActivity">
|
||||
android:name=".ui.UrlSchemeActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -143,16 +145,16 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.AboutActivity" />
|
||||
android:name=".ui.AboutActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".service.V2RayVpnService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
@@ -168,8 +170,8 @@
|
||||
<service
|
||||
android:name=".service.V2RayProxyOnlyService"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="@string/app_name"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
@@ -179,12 +181,11 @@
|
||||
<service
|
||||
android:name=".service.V2RayTestService"
|
||||
android:exported="false"
|
||||
android:process=":RunSoLibV2RayDaemon"
|
||||
/>
|
||||
android:process=":RunSoLibV2RayDaemon" />
|
||||
|
||||
<receiver
|
||||
android:exported="true"
|
||||
android:name=".receiver.WidgetProvider"
|
||||
android:exported="true"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
@@ -195,13 +196,21 @@
|
||||
<action android:name="com.v2ray.ang.action.activity" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.BootReceiver"
|
||||
android:exported="true"
|
||||
android:label="BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:exported="true"
|
||||
android:name=".service.QSTileService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:label="@string/app_tile_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
@@ -213,8 +222,8 @@
|
||||
</service>
|
||||
<!-- =====================Tasker===================== -->
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".ui.TaskerActivity"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
@@ -223,8 +232,8 @@
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:exported="true"
|
||||
android:name=".receiver.TaskerReceiver"
|
||||
android:exported="true"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
|
||||
73
V2rayNG/app/src/main/assets/custom_routing_black
Normal file
73
V2rayNG/app/src/main/assets/custom_routing_black
Normal file
@@ -0,0 +1,73 @@
|
||||
[
|
||||
{
|
||||
"remarks": "绕过bittorrent",
|
||||
"outboundTag": "direct",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "Google cn",
|
||||
"outboundTag": "proxy",
|
||||
"domain": [
|
||||
"domain:googleapis.cn",
|
||||
"domain:gstatic.com"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "阻断udp443",
|
||||
"outboundTag": "block",
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网域名",
|
||||
"outboundTag": "direct",
|
||||
"domain": [
|
||||
"geosite:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "代理GFW",
|
||||
"outboundTag": "proxy",
|
||||
"domain": [
|
||||
"geosite:gfw",
|
||||
"geosite:greatfire"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "代理Google等",
|
||||
"outboundTag": "proxy",
|
||||
"ip": [
|
||||
"1.0.0.1",
|
||||
"1.1.1.1",
|
||||
"8.8.8.8",
|
||||
"8.8.4.4",
|
||||
"geoip:facebook",
|
||||
"geoip:fastly",
|
||||
"geoip:google",
|
||||
"geoip:netflix",
|
||||
"geoip:telegram",
|
||||
"geoip:twitter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "最终直连",
|
||||
"port": "0-65535",
|
||||
"outboundTag": "direct"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
geosite:category-ads-all,
|
||||
@@ -1 +0,0 @@
|
||||
geosite:cn
|
||||
34
V2rayNG/app/src/main/assets/custom_routing_global
Normal file
34
V2rayNG/app/src/main/assets/custom_routing_global
Normal file
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"remarks": "阻断udp443",
|
||||
"outboundTag": "block",
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网域名",
|
||||
"outboundTag": "direct",
|
||||
"domain": [
|
||||
"geosite:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "最终代理",
|
||||
"port": "0-65535",
|
||||
"outboundTag": "proxy"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
geosite:geolocation-!cn
|
||||
82
V2rayNG/app/src/main/assets/custom_routing_white
Normal file
82
V2rayNG/app/src/main/assets/custom_routing_white
Normal file
@@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"remarks": "Google cn",
|
||||
"outboundTag": "proxy",
|
||||
"domain": [
|
||||
"domain:googleapis.cn",
|
||||
"domain:gstatic.com"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "阻断udp443",
|
||||
"outboundTag": "block",
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网域名",
|
||||
"outboundTag": "direct",
|
||||
"domain": [
|
||||
"geosite:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过中国域名",
|
||||
"outboundTag": "direct",
|
||||
"domain": [
|
||||
"domain:dns.alidns.com",
|
||||
"domain:dns.pub",
|
||||
"domain:doh.pub",
|
||||
"domain:dot.pub",
|
||||
"domain:doh.360.cn",
|
||||
"domain:dot.360.cn",
|
||||
"geosite:cn",
|
||||
"geosite:geolocation-cn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过中国IP",
|
||||
"outboundTag": "direct",
|
||||
"ip": [
|
||||
"223.5.5.5/32",
|
||||
"223.6.6.6/32",
|
||||
"2400:3200::1/128",
|
||||
"2400:3200:baba::1/128",
|
||||
"119.29.29.29/32",
|
||||
"1.12.12.12/32",
|
||||
"120.53.53.53/32",
|
||||
"2402:4e00::/128",
|
||||
"2402:4e00:1::/128",
|
||||
"180.76.76.76/32",
|
||||
"2400:da00::6666/128",
|
||||
"114.114.114.114/32",
|
||||
"114.114.115.115/32",
|
||||
"180.184.1.1/32",
|
||||
"180.184.2.2/32",
|
||||
"101.226.4.6/32",
|
||||
"218.30.118.6/32",
|
||||
"123.125.81.6/32",
|
||||
"140.207.198.6/32",
|
||||
"geoip:cn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "最终代理",
|
||||
"port": "0-65535",
|
||||
"outboundTag": "proxy"
|
||||
}
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Listener for manual initiation of a drag.
|
||||
*/
|
||||
public interface OnStartDragListener {
|
||||
|
||||
/**
|
||||
* Called when a view is requesting a start of a drag.
|
||||
*
|
||||
* @param viewHolder The holder of the view to drag.
|
||||
*/
|
||||
void onStartDrag(RecyclerView.ViewHolder viewHolder);
|
||||
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class AngApplication : MultiDexApplication() {
|
||||
@@ -32,11 +35,18 @@ class AngApplication : MultiDexApplication() {
|
||||
// if (firstRun)
|
||||
// defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
||||
|
||||
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
||||
MMKV.initialize(this)
|
||||
|
||||
Utils.setNightMode(application)
|
||||
// Initialize WorkManager with the custom configuration
|
||||
WorkManager.initialize(this, workManagerConfiguration)
|
||||
|
||||
SettingsManager.initRoutingRulesets(this)
|
||||
}
|
||||
|
||||
fun getPackageInfo(packageName: String) = packageManager.getPackageInfo(
|
||||
packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
|
||||
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
|
||||
)!!
|
||||
|
||||
}
|
||||
|
||||
@@ -25,11 +25,7 @@ object AppConfig {
|
||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||
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_BLOCKED = "pref_v2ray_routing_blocked"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
|
||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||
const val PREF_MUX_CONCURRENCY = "pref_mux_concurrency"
|
||||
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurrency"
|
||||
@@ -52,12 +48,12 @@ object AppConfig {
|
||||
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
const val PREF_HTTP_PORT = "pref_http_port"
|
||||
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_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_MODE = "pref_mode"
|
||||
const val PREF_IS_BOOTED = "pref_is_booted"
|
||||
|
||||
/** Cache keys. */
|
||||
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
|
||||
@@ -109,6 +105,10 @@ object AppConfig {
|
||||
const val DNS_PROXY = "1.1.1.1"
|
||||
const val DNS_DIRECT = "223.5.5.5"
|
||||
const val DNS_VPN = "1.1.1.1"
|
||||
const val GEOSITE_PRIVATE = "geosite:private"
|
||||
const val GEOSITE_CN = "geosite:cn"
|
||||
const val GEOIP_PRIVATE = "geoip:private"
|
||||
const val GEOIP_CN = "geoip:cn"
|
||||
|
||||
/** Ports and addresses for various services. */
|
||||
const val PORT_LOCAL_DNS = "10853"
|
||||
@@ -117,6 +117,7 @@ object AppConfig {
|
||||
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
|
||||
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
||||
const val WIREGUARD_LOCAL_MTU = "1420"
|
||||
const val LOOPBACK = "127.0.0.1"
|
||||
|
||||
/** Message constants for communication. */
|
||||
const val MSG_REGISTER_CLIENT = 1
|
||||
@@ -137,7 +138,7 @@ object AppConfig {
|
||||
|
||||
/** 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 RAY_NG_CHANNEL_NAME = "v2rayNG Background Service"
|
||||
const val SUBSCRIPTION_UPDATE_CHANNEL = "subscription_update_channel"
|
||||
const val SUBSCRIPTION_UPDATE_CHANNEL_NAME = "Subscription Update Service"
|
||||
|
||||
@@ -146,7 +147,15 @@ object AppConfig {
|
||||
const val CUSTOM = ""
|
||||
const val SHADOWSOCKS = "ss://"
|
||||
const val SOCKS = "socks://"
|
||||
const val HTTP = "http://"
|
||||
const val VLESS = "vless://"
|
||||
const val TROJAN = "trojan://"
|
||||
const val WIREGUARD = "wireguard://"
|
||||
const val TUIC = "tuic://"
|
||||
const val HYSTERIA2 = "hysteria2://"
|
||||
const val HY2 = "hy2://"
|
||||
|
||||
/** Give a good name to this, IDK*/
|
||||
const val VPN = "VPN"
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class ConfigResult (
|
||||
var status: Boolean,
|
||||
var guid: String? = null,
|
||||
var content: String = "",
|
||||
var domainPort: String? = null,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,10 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
|
||||
SOCKS(4, AppConfig.SOCKS),
|
||||
VLESS(5, AppConfig.VLESS),
|
||||
TROJAN(6, AppConfig.TROJAN),
|
||||
WIREGUARD(7, AppConfig.WIREGUARD);
|
||||
WIREGUARD(7, AppConfig.WIREGUARD),
|
||||
// TUIC(8, AppConfig.TUIC),
|
||||
HYSTERIA2(9, AppConfig.HYSTERIA2),
|
||||
HTTP(10, AppConfig.HTTP);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
enum class ERoutingMode(val value: String) {
|
||||
GLOBAL_PROXY("0"),
|
||||
BYPASS_LAN("1"),
|
||||
BYPASS_MAINLAND("2"),
|
||||
BYPASS_LAN_MAINLAND("3"),
|
||||
GLOBAL_DIRECT("4");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class Hysteria2Bean(
|
||||
val server: String?,
|
||||
val auth: String?,
|
||||
val lazy: Boolean? = true,
|
||||
val obfs: ObfsBean? = null,
|
||||
val socks5: Socks5Bean? = null,
|
||||
val http: Socks5Bean? = null,
|
||||
val tls: TlsBean? = null,
|
||||
) {
|
||||
data class ObfsBean(
|
||||
val type: String?,
|
||||
val salamander: SalamanderBean?
|
||||
) {
|
||||
data class SalamanderBean(
|
||||
val password: String?,
|
||||
)
|
||||
}
|
||||
|
||||
data class Socks5Bean(
|
||||
val listen: String?,
|
||||
)
|
||||
|
||||
data class TlsBean(
|
||||
val sni: String?,
|
||||
val insecure: Boolean?,
|
||||
)
|
||||
}
|
||||
13
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt
Normal file
13
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class RulesetItem(
|
||||
var remarks: String? = "",
|
||||
var ip: List<String>? = null,
|
||||
var domain: List<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
var port: String? = null,
|
||||
var network: String? = null,
|
||||
var protocol: List<String>? = null,
|
||||
var enabled: Boolean = true,
|
||||
var looked: Boolean? = false,
|
||||
)
|
||||
@@ -3,7 +3,6 @@ package com.v2ray.ang.dto
|
||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
data class ServerConfig(
|
||||
val configVersion: Int = 3,
|
||||
@@ -17,7 +16,8 @@ data class ServerConfig(
|
||||
companion object {
|
||||
fun create(configType: EConfigType): ServerConfig {
|
||||
when (configType) {
|
||||
EConfigType.VMESS, EConfigType.VLESS ->
|
||||
EConfigType.VMESS,
|
||||
EConfigType.VLESS ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
@@ -36,7 +36,11 @@ data class ServerConfig(
|
||||
EConfigType.CUSTOM ->
|
||||
return ServerConfig(configType = configType)
|
||||
|
||||
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||
EConfigType.SHADOWSOCKS,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
EConfigType.TROJAN,
|
||||
EConfigType.HYSTERIA2 ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
@@ -79,10 +83,4 @@ data class ServerConfig(
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
fun getV2rayPointDomainAndPort(): String {
|
||||
val address = getProxyOutbound()?.getServerAddress().orEmpty()
|
||||
val port = getProxyOutbound()?.getServerPort()
|
||||
return Utils.getIpv6Address(address) + ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,8 @@ data class SubscriptionItem(
|
||||
var lastUpdated: Long = -1,
|
||||
var autoUpdate: Boolean = false,
|
||||
val updateInterval: Int? = null,
|
||||
var prevProfile: String? = null,
|
||||
var nextProfile: String? = null,
|
||||
var filter: String? = null,
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.lang.reflect.Type
|
||||
|
||||
data class V2rayConfig(
|
||||
@@ -85,7 +86,7 @@ data class V2rayConfig(
|
||||
data class OutSettingsBean(
|
||||
var vnext: List<VnextBean>? = null,
|
||||
var fragment: FragmentBean? = null,
|
||||
var noise: NoiseBean? = null,
|
||||
var noises: List<NoiseBean>? = null,
|
||||
var servers: List<ServersBean>? = null,
|
||||
/*Blackhole*/
|
||||
var response: Response? = null,
|
||||
@@ -103,7 +104,8 @@ data class V2rayConfig(
|
||||
var secretKey: String? = null,
|
||||
val peers: List<WireGuardBean>? = null,
|
||||
var reserved: List<Int>? = null,
|
||||
var mtu: Int? = null
|
||||
var mtu: Int? = null,
|
||||
var obfsPassword: String? = null,
|
||||
) {
|
||||
|
||||
data class VnextBean(
|
||||
@@ -129,15 +131,16 @@ data class V2rayConfig(
|
||||
)
|
||||
|
||||
data class NoiseBean(
|
||||
var type: String? = null,
|
||||
var packet: String? = null,
|
||||
var delay: String? = null
|
||||
)
|
||||
|
||||
data class ServersBean(
|
||||
var address: String = "",
|
||||
var method: String = "chacha20-poly1305",
|
||||
var method: String? = null,
|
||||
var ota: Boolean = false,
|
||||
var password: String = "",
|
||||
var password: String? = null,
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
val email: String? = null,
|
||||
@@ -145,8 +148,6 @@ data class V2rayConfig(
|
||||
val ivCheck: Boolean? = null,
|
||||
var users: List<SocksUsersBean>? = null
|
||||
) {
|
||||
|
||||
|
||||
data class SocksUsersBean(
|
||||
var user: String = "",
|
||||
var pass: String = "",
|
||||
@@ -175,6 +176,7 @@ data class V2rayConfig(
|
||||
var quicSettings: QuicSettingBean? = null,
|
||||
var realitySettings: TlsSettingsBean? = null,
|
||||
var grpcSettings: GrpcSettingsBean? = null,
|
||||
var hy2steriaSettings: Hy2steriaSettingsBean? = null,
|
||||
val dsSettings: Any? = null,
|
||||
var sockopt: SockoptBean? = null
|
||||
) {
|
||||
@@ -293,6 +295,18 @@ data class V2rayConfig(
|
||||
var health_check_timeout: Int? = null
|
||||
)
|
||||
|
||||
data class Hy2steriaSettingsBean(
|
||||
var password: String? = null,
|
||||
var use_udp_extension: Boolean? = true,
|
||||
var congestion: Hy2CongestionBean? = null
|
||||
) {
|
||||
data class Hy2CongestionBean(
|
||||
var type: String? = "bbr",
|
||||
var up_mbps: Int? = null,
|
||||
var down_mbps: Int? = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun populateTransportSettings(
|
||||
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
||||
@@ -423,7 +437,9 @@ data class V2rayConfig(
|
||||
return settings?.vnext?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
@@ -439,7 +455,9 @@ data class V2rayConfig(
|
||||
return settings?.vnext?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
@@ -448,6 +466,12 @@ data class V2rayConfig(
|
||||
return null
|
||||
}
|
||||
|
||||
fun getServerAddressAndPort(): String {
|
||||
val address = getServerAddress().orEmpty()
|
||||
val port = getServerPort()
|
||||
return Utils.getIpv6Address(address) + ":" + port
|
||||
}
|
||||
|
||||
fun getPassword(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||
@@ -455,9 +479,12 @@ data class V2rayConfig(
|
||||
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.password
|
||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
|
||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
return settings?.secretKey
|
||||
@@ -486,8 +513,8 @@ data class V2rayConfig(
|
||||
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
||||
listOf(
|
||||
tcpSetting.header.type,
|
||||
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
|
||||
tcpSetting.header.request?.path?.joinToString().orEmpty()
|
||||
tcpSetting.header.request?.headers?.Host?.joinToString(",").orEmpty(),
|
||||
tcpSetting.header.request?.path?.joinToString(",").orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -531,7 +558,7 @@ data class V2rayConfig(
|
||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||
listOf(
|
||||
"",
|
||||
h2Setting.host.joinToString(),
|
||||
h2Setting.host.joinToString(","),
|
||||
h2Setting.path
|
||||
)
|
||||
}
|
||||
@@ -586,6 +613,7 @@ data class V2rayConfig(
|
||||
) {
|
||||
|
||||
data class RulesBean(
|
||||
var type: String = "field",
|
||||
var ip: ArrayList<String>? = null,
|
||||
var domain: ArrayList<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.v2ray.ang.extension
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import org.json.JSONObject
|
||||
import java.io.Serializable
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
|
||||
@@ -54,4 +59,38 @@ val URLConnection.responseLength: Long
|
||||
val URI.idnHost: String
|
||||
get() = host?.replace("[", "")?.replace("]", "").orEmpty()
|
||||
|
||||
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
|
||||
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
|
||||
|
||||
fun String.toLongEx(): Long = toLongOrNull() ?: 0
|
||||
|
||||
fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
callback()
|
||||
if (onetime) context.unregisterReceiver(this)
|
||||
}
|
||||
}.apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(this, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
}, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
registerReceiver(this, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
|
||||
else -> @Suppress("DEPRECATION") getSerializable(key) as? T
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Intent.serializable(key: String): T? = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
|
||||
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
|
||||
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
import android.content.pm.ResolveInfo
|
||||
|
||||
class NativePlugin(resolveInfo: ResolveInfo) : ResolvedPlugin(resolveInfo) {
|
||||
init {
|
||||
check(resolveInfo.providerInfo != null)
|
||||
}
|
||||
|
||||
override val componentInfo get() = resolveInfo.providerInfo!!
|
||||
}
|
||||
43
V2rayNG/app/src/main/kotlin/com/v2ray/ang/plugin/Plugin.kt
Normal file
43
V2rayNG/app/src/main/kotlin/com/v2ray/ang/plugin/Plugin.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
|
||||
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
abstract class Plugin {
|
||||
abstract val id: String
|
||||
abstract val label: CharSequence
|
||||
abstract val version: Int
|
||||
abstract val versionName: String
|
||||
open val icon: Drawable? get() = null
|
||||
open val defaultConfig: String? get() = null
|
||||
open val packageName: String get() = ""
|
||||
open val directBootAware: Boolean get() = true
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return id == (other as Plugin).id
|
||||
}
|
||||
|
||||
override fun hashCode() = id.hashCode()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
object PluginContract {
|
||||
|
||||
const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN"
|
||||
const val EXTRA_ENTRY = "io.nekohasekai.sagernet.plugin.EXTRA_ENTRY"
|
||||
const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id"
|
||||
const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plugin.executable_path"
|
||||
const val METHOD_GET_EXECUTABLE = "sagernet:getExecutable"
|
||||
|
||||
const val COLUMN_PATH = "path"
|
||||
const val COLUMN_MODE = "mode"
|
||||
const val SCHEME = "plugin"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
|
||||
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import com.v2ray.ang.AngApplication
|
||||
|
||||
class PluginList : ArrayList<Plugin>() {
|
||||
init {
|
||||
addAll(
|
||||
AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA)
|
||||
.filter { it.providerInfo.exported }.map { NativePlugin(it) })
|
||||
}
|
||||
|
||||
val lookup = mutableMapOf<String, Plugin>().apply {
|
||||
for (plugin in this@PluginList.toList()) {
|
||||
fun check(old: Plugin?) {
|
||||
if (old != null && old != plugin) {
|
||||
this@PluginList.remove(old)
|
||||
}
|
||||
/* if (old != null && old !== plugin) {
|
||||
val packages = this@PluginList.filter { it.id == plugin.id }
|
||||
.joinToString { it.packageName }
|
||||
val message = "Conflicting plugins found from: $packages"
|
||||
Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show()
|
||||
throw IllegalStateException(message)
|
||||
}*/
|
||||
}
|
||||
check(put(plugin.id, plugin))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-AngApplication@sekai.icu> *
|
||||
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ContentResolver
|
||||
import android.content.Intent
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.bundleOf
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.extension.listenForPackageChanges
|
||||
import com.v2ray.ang.plugin.PluginContract.METADATA_KEY_ID
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
object PluginManager {
|
||||
|
||||
class PluginNotFoundException(val plugin: String) : FileNotFoundException(plugin)
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
private var cachedPlugins: PluginList? = null
|
||||
fun fetchPlugins() = synchronized(this) {
|
||||
if (receiver == null) receiver = AngApplication.application.listenForPackageChanges {
|
||||
synchronized(this) {
|
||||
receiver = null
|
||||
cachedPlugins = null
|
||||
}
|
||||
}
|
||||
if (cachedPlugins == null) cachedPlugins = PluginList()
|
||||
cachedPlugins!!
|
||||
}
|
||||
|
||||
private fun buildUri(id: String, authority: String) = Uri.Builder()
|
||||
.scheme(PluginContract.SCHEME)
|
||||
.authority(authority)
|
||||
.path("/$id")
|
||||
.build()
|
||||
|
||||
data class InitResult(
|
||||
val path: String,
|
||||
)
|
||||
|
||||
@Throws(Throwable::class)
|
||||
fun init(pluginId: String): InitResult? {
|
||||
if (pluginId.isEmpty()) return null
|
||||
var throwable: Throwable? = null
|
||||
|
||||
try {
|
||||
val result = initNative(pluginId)
|
||||
if (result != null) return result
|
||||
} catch (t: Throwable) {
|
||||
if (throwable == null) throwable = t //Logs.w(t)
|
||||
}
|
||||
|
||||
throw throwable ?: PluginNotFoundException(pluginId)
|
||||
}
|
||||
|
||||
private fun initNative(pluginId: String): InitResult? {
|
||||
var flags = PackageManager.GET_META_DATA
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
flags =
|
||||
flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
}
|
||||
var providers = AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "com.github.dyhkwong.AngApplication")), flags)
|
||||
.filter { it.providerInfo.exported }
|
||||
if (providers.isEmpty()) {
|
||||
providers = AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "io.nekohasekai.AngApplication")), flags)
|
||||
.filter { it.providerInfo.exported }
|
||||
}
|
||||
if (providers.isEmpty()) {
|
||||
providers = AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "moe.matsuri.lite")), flags)
|
||||
.filter { it.providerInfo.exported }
|
||||
}
|
||||
if (providers.isEmpty()) {
|
||||
providers = AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "fr.husi")), flags)
|
||||
.filter { it.providerInfo.exported }
|
||||
}
|
||||
if (providers.isEmpty()) {
|
||||
providers = AngApplication.application.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA
|
||||
).filter {
|
||||
it.providerInfo.exported &&
|
||||
it.providerInfo.metaData.containsKey(METADATA_KEY_ID) &&
|
||||
it.providerInfo.metaData.getString(METADATA_KEY_ID) == pluginId
|
||||
}
|
||||
if (providers.size > 1) {
|
||||
providers = listOf(providers[0]) // What if there is more than one?
|
||||
}
|
||||
}
|
||||
if (providers.isEmpty()) return null
|
||||
if (providers.size > 1) {
|
||||
val message =
|
||||
"Conflicting plugins found from: ${providers.joinToString { it.providerInfo.packageName }}"
|
||||
Toast.makeText(AngApplication.application, message, Toast.LENGTH_LONG).show()
|
||||
throw IllegalStateException(message)
|
||||
}
|
||||
val provider = providers.single().providerInfo
|
||||
var failure: Throwable? = null
|
||||
try {
|
||||
initNativeFaster(provider)?.also { return InitResult(it) }
|
||||
} catch (t: Throwable) {
|
||||
// Logs.w("Initializing native plugin faster mode failed")
|
||||
failure = t
|
||||
}
|
||||
|
||||
val uri = Uri.Builder().apply {
|
||||
scheme(ContentResolver.SCHEME_CONTENT)
|
||||
authority(provider.authority)
|
||||
}.build()
|
||||
try {
|
||||
return initNativeFast(AngApplication.application.contentResolver,
|
||||
pluginId,
|
||||
uri)?.let { InitResult(it) }
|
||||
} catch (t: Throwable) {
|
||||
// Logs.w("Initializing native plugin fast mode failed")
|
||||
failure?.also { t.addSuppressed(it) }
|
||||
failure = t
|
||||
}
|
||||
|
||||
try {
|
||||
return initNativeSlow(AngApplication.application.contentResolver,
|
||||
pluginId,
|
||||
uri)?.let { InitResult(it) }
|
||||
} catch (t: Throwable) {
|
||||
failure?.also { t.addSuppressed(it) }
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNativeFaster(provider: ProviderInfo): String? {
|
||||
return provider.loadString(PluginContract.METADATA_KEY_EXECUTABLE_PATH)
|
||||
?.let { relativePath ->
|
||||
File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {
|
||||
check(canExecute())
|
||||
}.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNativeFast(cr: ContentResolver, pluginId: String, uri: Uri): String? {
|
||||
return cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null, bundleOf())
|
||||
?.getString(PluginContract.EXTRA_ENTRY)?.also {
|
||||
check(File(it).canExecute())
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("Recycle")
|
||||
private fun initNativeSlow(cr: ContentResolver, pluginId: String, uri: Uri): String? {
|
||||
var initialized = false
|
||||
fun entryNotFound(): Nothing =
|
||||
throw IndexOutOfBoundsException("Plugin entry binary not found")
|
||||
|
||||
val pluginDir = File(AngApplication.application.noBackupFilesDir, "plugin")
|
||||
(cr.query(uri,
|
||||
arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE),
|
||||
null,
|
||||
null,
|
||||
null)
|
||||
?: return null).use { cursor ->
|
||||
if (!cursor.moveToFirst()) entryNotFound()
|
||||
pluginDir.deleteRecursively()
|
||||
if (!pluginDir.mkdirs()) throw FileNotFoundException("Unable to create plugin directory")
|
||||
val pluginDirPath = pluginDir.absolutePath + '/'
|
||||
do {
|
||||
val path = cursor.getString(0)
|
||||
val file = File(pluginDir, path)
|
||||
check(file.absolutePath.startsWith(pluginDirPath))
|
||||
cr.openInputStream(uri.buildUpon().path(path).build())!!.use { inStream ->
|
||||
file.outputStream().use { outStream -> inStream.copyTo(outStream) }
|
||||
}
|
||||
Os.chmod(file.absolutePath, when (cursor.getType(1)) {
|
||||
Cursor.FIELD_TYPE_INTEGER -> cursor.getInt(1)
|
||||
Cursor.FIELD_TYPE_STRING -> cursor.getString(1).toInt(8)
|
||||
else -> throw IllegalArgumentException("File mode should be of type int")
|
||||
})
|
||||
if (path == pluginId) initialized = true
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
if (!initialized) entryNotFound()
|
||||
return File(pluginDir, pluginId).absolutePath
|
||||
}
|
||||
|
||||
fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {
|
||||
is String -> value
|
||||
is Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
|
||||
.getString(value)
|
||||
null -> null
|
||||
else -> error("meta-data $key has invalid type ${value.javaClass}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
|
||||
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
package com.v2ray.ang.plugin
|
||||
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.plugin.PluginManager.loadString
|
||||
|
||||
abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
|
||||
protected abstract val componentInfo: ComponentInfo
|
||||
|
||||
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
|
||||
override val version by lazy {
|
||||
AngApplication.application.getPackageInfo(componentInfo.packageName).versionCode
|
||||
}
|
||||
override val versionName: String by lazy {
|
||||
AngApplication.application.getPackageInfo(componentInfo.packageName).versionName!!
|
||||
}
|
||||
override val label: CharSequence get() = resolveInfo.loadLabel(AngApplication.application.packageManager)
|
||||
override val icon: Drawable get() = resolveInfo.loadIcon(AngApplication.application.packageManager)
|
||||
override val packageName: String get() = componentInfo.packageName
|
||||
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.v2ray.ang.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED == intent?.action && MmkvManager.decodeStartOnBoot()) {
|
||||
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(context!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,12 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class TaskerReceiver : BroadcastReceiver() {
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
|
||||
@@ -27,7 +24,7 @@ class TaskerReceiver : BroadcastReceiver() {
|
||||
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
||||
Utils.startVServiceFromToggle(context)
|
||||
} else {
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
MmkvManager.setSelectServer(guid)
|
||||
V2RayServiceManager.startV2Ray(context)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProcessService {
|
||||
private val TAG = ANG_PACKAGE
|
||||
private lateinit var process: Process
|
||||
|
||||
fun runProcess(context: Context, cmd: MutableList<String>) {
|
||||
Log.d(TAG, cmd.toString())
|
||||
|
||||
try {
|
||||
val proBuilder = ProcessBuilder(cmd)
|
||||
proBuilder.redirectErrorStream(true)
|
||||
process = proBuilder
|
||||
.directory(context.filesDir)
|
||||
.start()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Thread.sleep(50L)
|
||||
Log.d(TAG, "runProcess check")
|
||||
process.waitFor()
|
||||
Log.d(TAG, "runProcess exited")
|
||||
}
|
||||
Log.d(TAG, process.toString())
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun stopProcess() {
|
||||
try {
|
||||
Log.d(TAG, "runProcess destroy")
|
||||
process?.destroy()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ 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.util.AngConfigManager
|
||||
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
@@ -40,8 +41,8 @@ object SubscriptionUpdater {
|
||||
|
||||
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
|
||||
|
||||
for (i in subs) {
|
||||
val subscription = i.second
|
||||
for (sub in subs) {
|
||||
val subItem = sub.second
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
|
||||
@@ -56,11 +57,10 @@ object SubscriptionUpdater {
|
||||
notificationManager.notify(3, notification.build())
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"subscription automatic update: ---${subscription.remarks}"
|
||||
"subscription automatic update: ---${subItem.remarks}"
|
||||
)
|
||||
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
||||
AngConfigManager.importBatchConfig(configs, i.first, false)
|
||||
notification.setContentText("Updating ${subscription.remarks}")
|
||||
updateConfigViaSub(Pair(sub.first, subItem))
|
||||
notification.setContentText("Updating ${subItem.remarks}")
|
||||
}
|
||||
notificationManager.cancel(3)
|
||||
return Result.success()
|
||||
|
||||
@@ -13,10 +13,10 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.AppConfig.VPN
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
@@ -24,6 +24,8 @@ import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.PluginUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import go.Seq
|
||||
@@ -46,8 +48,6 @@ object V2RayServiceManager {
|
||||
|
||||
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
||||
private val mMsgReceive = ReceiveMessageHandler()
|
||||
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) }
|
||||
|
||||
var serviceControl: SoftReference<ServiceControl>? = null
|
||||
set(value) {
|
||||
@@ -64,7 +64,7 @@ object V2RayServiceManager {
|
||||
|
||||
fun startV2Ray(context: Context) {
|
||||
if (v2rayPoint.isRunning) return
|
||||
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||
val guid = MmkvManager.getSelectServer() ?: return
|
||||
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||
if (!result.status) return
|
||||
|
||||
@@ -73,7 +73,7 @@ object V2RayServiceManager {
|
||||
} else {
|
||||
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)
|
||||
} else {
|
||||
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
|
||||
@@ -108,13 +108,11 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||
//Logger.d(s)
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setup(s: String): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return -1
|
||||
//Logger.d(s)
|
||||
return try {
|
||||
serviceControl.startService()
|
||||
lastQueryTime = System.currentTimeMillis()
|
||||
@@ -129,7 +127,7 @@ object V2RayServiceManager {
|
||||
|
||||
fun startV2rayPoint() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||
val guid = MmkvManager.getSelectServer() ?: return
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||
if (v2rayPoint.isRunning) {
|
||||
return
|
||||
@@ -153,7 +151,7 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
v2rayPoint.configureFileContent = result.content
|
||||
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
||||
v2rayPoint.domainName = result.domainPort
|
||||
currentConfig = config
|
||||
|
||||
try {
|
||||
@@ -165,6 +163,8 @@ object V2RayServiceManager {
|
||||
if (v2rayPoint.isRunning) {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||
showNotification()
|
||||
|
||||
PluginUtil.runPlugin(service, config, result.domainPort)
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||
cancelNotification()
|
||||
@@ -192,6 +192,7 @@ object V2RayServiceManager {
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
PluginUtil.stopPlugin()
|
||||
}
|
||||
|
||||
private class ReceiveMessageHandler : BroadcastReceiver() {
|
||||
@@ -199,7 +200,6 @@ object V2RayServiceManager {
|
||||
val serviceControl = serviceControl?.get() ?: return
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_REGISTER_CLIENT -> {
|
||||
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
|
||||
if (v2rayPoint.isRunning) {
|
||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
|
||||
} else {
|
||||
|
||||
@@ -6,9 +6,14 @@ import android.os.IBinder
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.serializable
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.PluginUtil
|
||||
import com.v2ray.ang.util.SpeedtestUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import go.Seq
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -30,10 +35,10 @@ class V2RayTestService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
MSG_MEASURE_CONFIG -> {
|
||||
val contentPair = intent.getSerializableExtra("content") as Pair<String, String>
|
||||
val guid = intent.serializable<String>("content") ?: ""
|
||||
realTestScope.launch {
|
||||
val result = SpeedtestUtil.realPing(contentPair.second)
|
||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
||||
val result = startRealPing(guid)
|
||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(guid, result))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +52,20 @@ class V2RayTestService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun startRealPing(guid: String): Long {
|
||||
val retFailure = -1L
|
||||
|
||||
val server = MmkvManager.decodeServerConfig(guid) ?: return retFailure
|
||||
if (server.getProxyOutbound()?.protocol?.equals(EConfigType.HYSTERIA2.name, true) == true) {
|
||||
val delay = PluginUtil.realPingHy2(this, server)
|
||||
return delay
|
||||
} else {
|
||||
val config = V2rayConfigUtil.getV2rayConfig(this, guid)
|
||||
if (!config.status) {
|
||||
return retFailure
|
||||
}
|
||||
return SpeedtestUtil.realPing(config.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@ import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.ERoutingMode
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.MyContextWrapper
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -39,7 +40,6 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private const val TUN2SOCKS = "libtun2socks.so"
|
||||
}
|
||||
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private lateinit var mInterface: ParcelFileDescriptor
|
||||
private var isRunning = false
|
||||
@@ -117,13 +117,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
val builder = Builder()
|
||||
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
||||
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||
|
||||
builder.setMtu(VPN_MTU)
|
||||
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
|
||||
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||
if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||
val bypassLan = SettingsManager.routingRulesetsBypassLan()
|
||||
if (bypassLan) {
|
||||
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
|
||||
val addr = it.split('/')
|
||||
builder.addRoute(addr[0], addr[1].toInt())
|
||||
@@ -134,29 +132,32 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
||||
if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||
if (bypassLan) {
|
||||
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
|
||||
} else {
|
||||
builder.addRoute("::", 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||
builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||
} else {
|
||||
Utils.getVpnDnsServers()
|
||||
.forEach {
|
||||
if (Utils.isPureIpAddress(it)) {
|
||||
builder.addDnsServer(it)
|
||||
}
|
||||
// if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||
// } else {
|
||||
Utils.getVpnDnsServers()
|
||||
.forEach {
|
||||
if (Utils.isPureIpAddress(it)) {
|
||||
builder.addDnsServer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
||||
|
||||
val selfPackageName = BuildConfig.APPLICATION_ID
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
||||
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
|
||||
//process self package
|
||||
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
|
||||
apps?.forEach {
|
||||
try {
|
||||
if (bypassApps)
|
||||
@@ -164,9 +165,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
else
|
||||
builder.addAllowedApplication(it)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
//Logger.d(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.addDisallowedApplication(selfPackageName)
|
||||
}
|
||||
|
||||
// Close the old interface since the parameters have been changed.
|
||||
@@ -201,12 +203,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
private fun runTun2socks() {
|
||||
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
val cmd = arrayListOf(
|
||||
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
||||
"--netif-netmask", "255.255.255.252",
|
||||
"--socks-server-addr", "127.0.0.1:${socksPort}",
|
||||
"--socks-server-addr", "$LOOPBACK:${socksPort}",
|
||||
"--tunmtu", VPN_MTU.toString(),
|
||||
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
|
||||
"--enable-udprelay",
|
||||
@@ -220,7 +222,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
||||
cmd.add("--dnsgw")
|
||||
cmd.add("127.0.0.1:${localDnsPort}")
|
||||
cmd.add("$LOOPBACK:${localDnsPort}")
|
||||
}
|
||||
Log.d(packageName, cmd.toString())
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.VPN
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
@@ -37,6 +38,7 @@ import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.viewmodel.MainViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
@@ -87,7 +89,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
binding.fab.setOnClickListener {
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
Utils.stopVService(this)
|
||||
} else if ((MmkvManager.settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||
} else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
startV2Ray()
|
||||
@@ -111,11 +113,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
val callback = SimpleItemTouchHelperCallback(adapter)
|
||||
mItemTouchHelper = ItemTouchHelper(callback)
|
||||
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
|
||||
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
||||
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||
)
|
||||
@@ -198,7 +198,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
fun startV2Ray() {
|
||||
if (MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
|
||||
toast(R.string.title_file_chooser)
|
||||
return
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(this)
|
||||
@@ -278,6 +279,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_http -> {
|
||||
importManually(EConfigType.HTTP.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_trojan -> {
|
||||
importManually(EConfigType.TROJAN.value)
|
||||
true
|
||||
@@ -288,6 +294,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_hysteria2 -> {
|
||||
importManually(EConfigType.HYSTERIA2.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_config_custom_clipboard -> {
|
||||
importConfigCustomClipboard()
|
||||
true
|
||||
@@ -706,10 +717,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
)
|
||||
}
|
||||
|
||||
R.id.user_asset_setting -> {
|
||||
startActivity(Intent(this, UserAssetActivity::class.java))
|
||||
R.id.routing_setting -> {
|
||||
requestSubSettingActivity.launch(Intent(this, RoutingSettingActivity::class.java))
|
||||
}
|
||||
|
||||
|
||||
R.id.promotion -> {
|
||||
Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AngApplication.Companion.application
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
@@ -17,13 +16,13 @@ import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
@@ -66,17 +65,12 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
} else {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||
}
|
||||
if (guid == MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (guid == MmkvManager.getSelectServer()) {
|
||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
|
||||
} else {
|
||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
|
||||
}
|
||||
holder.itemMainBinding.tvSubscription.text = ""
|
||||
val json = MmkvManager.subStorage?.decodeString(profile.subscriptionId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
holder.itemMainBinding.tvSubscription.text = sub.remarks
|
||||
}
|
||||
holder.itemMainBinding.tvSubscription.text = MmkvManager.decodeSubscription(profile.subscriptionId)?.remarks ?: ""
|
||||
|
||||
var shareOptions = share_method.asList()
|
||||
when (profile.configType) {
|
||||
@@ -85,16 +79,20 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
}
|
||||
|
||||
EConfigType.VLESS -> {
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name
|
||||
}
|
||||
|
||||
else -> {
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name.lowercase()
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name
|
||||
}
|
||||
}
|
||||
|
||||
val strState = "${profile.server?.dropLast(3)}*** : ${profile.serverPort}"
|
||||
// 隐藏主页服务器地址为xxx:xxx:***/xxx.xxx.xxx.***
|
||||
val strState = "${
|
||||
profile.server?.let {
|
||||
if (it.contains(":"))
|
||||
it.split(":").take(2).joinToString(":", postfix = ":***")
|
||||
else
|
||||
it.split('.').dropLast(1).joinToString(".", postfix = ".***")
|
||||
}
|
||||
} : ${profile.serverPort}"
|
||||
|
||||
holder.itemMainBinding.tvStatistics.text = strState
|
||||
|
||||
@@ -139,8 +137,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
}
|
||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (MmkvManager.settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
if (guid != MmkvManager.getSelectServer()) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
removeServer(guid, position)
|
||||
@@ -158,9 +156,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
holder.itemMainBinding.infoContainer.setOnClickListener {
|
||||
val selected = MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
val selected = MmkvManager.getSelectServer()
|
||||
if (guid != selected) {
|
||||
MmkvManager.mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
MmkvManager.setSelectServer(guid)
|
||||
if (!TextUtils.isEmpty(selected)) {
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected.orEmpty()))
|
||||
}
|
||||
@@ -236,31 +234,16 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
|
||||
BaseViewHolder(itemFooterBinding.root)
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
||||
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
// mActivity.alert(R.string.del_config_comfirm) {
|
||||
// positiveButton(android.R.string.ok) {
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
notifyItemRemoved(position)
|
||||
// }
|
||||
// show()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
// position is changed, since position is used by click callbacks, need to update range
|
||||
if (toPosition > fromPosition)
|
||||
notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
|
||||
else
|
||||
notifyItemRangeChanged(toPosition, fromPosition - toPosition + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.appcompat.widget.SearchView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
@@ -19,7 +18,7 @@ import com.v2ray.ang.dto.AppInfo
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
@@ -34,7 +33,6 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
private var adapter: PerAppProxyAdapter? = null
|
||||
private var appsAll: List<AppInfo>? = null
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingEditBinding
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoutingEditActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivityRoutingEditBinding.inflate(layoutInflater) }
|
||||
private val position by lazy { intent.getIntExtra("position", -1) }
|
||||
|
||||
private val outbound_tag: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.outbound_tag)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.routing_settings_rule_title)
|
||||
|
||||
val rulesetItem = SettingsManager.getRoutingRuleset(position)
|
||||
if (rulesetItem != null) {
|
||||
bindingServer(rulesetItem)
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindingServer(rulesetItem: RulesetItem): Boolean {
|
||||
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
|
||||
binding.chkLocked.isChecked = rulesetItem.looked ?: false
|
||||
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
|
||||
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
|
||||
binding.etPort.text = Utils.getEditable(rulesetItem.port)
|
||||
binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString(","))
|
||||
binding.etNetwork.text = Utils.getEditable(rulesetItem.network)
|
||||
val outbound = Utils.arrayFind(outbound_tag, rulesetItem.outboundTag)
|
||||
binding.spOutboundTag.setSelection(outbound)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun clearServer(): Boolean {
|
||||
binding.etRemarks.text = null
|
||||
binding.spOutboundTag.setSelection(0)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun saveServer(): Boolean {
|
||||
val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem()
|
||||
|
||||
rulesetItem.remarks = binding.etRemarks.text.toString()
|
||||
rulesetItem.looked = binding.chkLocked.isChecked
|
||||
binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
|
||||
binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
|
||||
binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
|
||||
binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } }
|
||||
binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } }
|
||||
rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition]
|
||||
|
||||
if (TextUtils.isEmpty(rulesetItem.remarks)) {
|
||||
toast(R.string.sub_setting_remarks)
|
||||
return false
|
||||
}
|
||||
|
||||
SettingsManager.saveRoutingRuleset(position, rulesetItem)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun deleteServer(): Boolean {
|
||||
if (position >= 0) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
SettingsManager.removeRoutingRuleset(position)
|
||||
launch(Dispatchers.Main) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
// do nothing
|
||||
}
|
||||
.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
val del_config = menu.findItem(R.id.del_config)
|
||||
|
||||
if (position < 0) {
|
||||
del_config?.isVisible = false
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.del_config -> {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoutingSettingActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivityRoutingSettingBinding.inflate(layoutInflater) }
|
||||
|
||||
var rulesets: MutableList<RulesetItem> = mutableListOf()
|
||||
private val adapter by lazy { RoutingSettingRecyclerAdapter(this) }
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val routing_domain_strategy: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.routing_domain_strategy)
|
||||
}
|
||||
private val preset_rulesets: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.preset_rulesets)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.routing_settings_title)
|
||||
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
|
||||
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
||||
|
||||
val found = Utils.arrayFind(routing_domain_strategy, settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "")
|
||||
found.let { binding.spDomainStrategy.setSelection(if (it >= 0) it else 0) }
|
||||
binding.spDomainStrategy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
settingsStorage.encode(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, routing_domain_strategy[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refreshData()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_routing_setting, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.add_rule -> {
|
||||
startActivity(Intent(this, RoutingEditActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
R.id.user_asset_setting -> {
|
||||
startActivity(Intent(this, UserAssetActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_rulesets -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
AlertDialog.Builder(this).setItems(preset_rulesets.asList().toTypedArray()) { _, i ->
|
||||
try {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
SettingsManager.resetRoutingRulesets(this@RoutingSettingActivity, i)
|
||||
launch(Dispatchers.Main) {
|
||||
refreshData()
|
||||
toast(R.string.toast_success)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}.show()
|
||||
|
||||
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_rulesets_from_clipboard -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
try {
|
||||
val clipboard = Utils.getClipboard(this)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val ret = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
|
||||
launch(Dispatchers.Main) {
|
||||
if (ret) {
|
||||
refreshData()
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.export_rulesets_to_clipboard -> {
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) {
|
||||
toast(R.string.toast_failure)
|
||||
} else {
|
||||
Utils.setClipboard(this, JsonUtil.toJson(rulesetList))
|
||||
toast(R.string.toast_success)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun refreshData() {
|
||||
rulesets = MmkvManager.decodeRoutingRulesets() ?: mutableListOf()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.v2ray.ang.databinding.ItemRecyclerRoutingSettingBinding
|
||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.ui.MainRecyclerAdapter.BaseViewHolder
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
|
||||
class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : RecyclerView.Adapter<RoutingSettingRecyclerAdapter.MainViewHolder>(),
|
||||
ItemTouchHelperAdapter {
|
||||
|
||||
private var mActivity: RoutingSettingActivity = activity
|
||||
override fun getItemCount() = mActivity.rulesets.size
|
||||
|
||||
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
|
||||
val ruleset = mActivity.rulesets[position]
|
||||
|
||||
holder.itemRoutingSettingBinding.remarks.text = ruleset.remarks
|
||||
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
|
||||
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
|
||||
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
|
||||
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.looked ?: false
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener {
|
||||
mActivity.startActivity(
|
||||
Intent(mActivity, RoutingEditActivity::class.java)
|
||||
.putExtra("position", position)
|
||||
)
|
||||
}
|
||||
|
||||
holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
||||
if( !it.isPressed) return@setOnCheckedChangeListener
|
||||
ruleset.enabled = isChecked
|
||||
SettingsManager.saveRoutingRuleset(position, ruleset)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||
return MainViewHolder(
|
||||
ItemRecyclerRoutingSettingBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class MainViewHolder(val itemRoutingSettingBinding: ItemRecyclerRoutingSettingBinding) :
|
||||
BaseViewHolder(itemRoutingSettingBinding.root), ItemTouchHelperViewHolder
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
fun onItemSelected() {
|
||||
itemView.setBackgroundColor(Color.LTGRAY)
|
||||
}
|
||||
|
||||
fun onItemClear() {
|
||||
itemView.setBackgroundColor(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
SettingsManager.swapRoutingRuleset(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
mActivity.refreshData()
|
||||
}
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
|
||||
|
||||
class RoutingSettingsActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivityRoutingSettingsBinding.inflate(layoutInflater) }
|
||||
|
||||
private val titles: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.routing_tag)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.title_pref_routing_custom)
|
||||
|
||||
val fragments = ArrayList<Fragment>()
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT))
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_DIRECT))
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_BLOCKED))
|
||||
|
||||
val adapter = FragmentAdapter(this, fragments)
|
||||
binding.viewpager.adapter = adapter
|
||||
//tablayout.setTabTextColors(Color.BLACK, Color.RED)
|
||||
TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position ->
|
||||
tab.text = titles[position]
|
||||
}.attach()
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
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.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoutingSettingsFragment : Fragment() {
|
||||
private val binding by lazy { FragmentRoutingSettingsBinding.inflate(layoutInflater) }
|
||||
|
||||
companion object {
|
||||
private const val routing_arg = "routing_arg"
|
||||
}
|
||||
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
// Inflate the layout for this fragment
|
||||
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
||||
}
|
||||
|
||||
fun newInstance(arg: String): Fragment {
|
||||
val fragment = RoutingSettingsFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.putString(routing_arg, arg)
|
||||
fragment.arguments = bundle
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
|
||||
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_routing, menu)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.save_routing -> {
|
||||
saveRouting()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.del_routing -> {
|
||||
binding.etRoutingContent.text = null
|
||||
true
|
||||
}
|
||||
|
||||
R.id.scan_replace -> {
|
||||
scanQRcode(true)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.scan_append -> {
|
||||
scanQRcode(false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.default_rules -> {
|
||||
setDefaultRules()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun saveRouting() {
|
||||
val content = binding.etRoutingContent.text.toString()
|
||||
settingsStorage?.encode(requireArguments().getString(routing_arg), content)
|
||||
activity?.toast(R.string.toast_success)
|
||||
}
|
||||
|
||||
fun scanQRcode(forReplace: Boolean): Boolean {
|
||||
// try {
|
||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
||||
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
|
||||
// } catch (e: Exception) {
|
||||
RxPermissions(requireActivity())
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
if (forReplace)
|
||||
scanQRCodeForReplace.launch(Intent(activity, ScannerActivity::class.java))
|
||||
else
|
||||
scanQRCodeForAppend.launch(Intent(activity, ScannerActivity::class.java))
|
||||
else
|
||||
activity?.toast(R.string.toast_permission_denied)
|
||||
}
|
||||
// }
|
||||
return true
|
||||
}
|
||||
|
||||
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||
}
|
||||
}
|
||||
|
||||
private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||
binding.etRoutingContent.text = Utils.getEditable("${binding.etRoutingContent.text},$content")
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultRules(): Boolean {
|
||||
var url = AppConfig.v2rayCustomRoutingListUrl
|
||||
var tag = ""
|
||||
when (requireArguments().getString(routing_arg)) {
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
||||
tag = AppConfig.TAG_PROXY
|
||||
}
|
||||
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
||||
tag = AppConfig.TAG_DIRECT
|
||||
}
|
||||
|
||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
||||
tag = AppConfig.TAG_BLOCKED
|
||||
}
|
||||
}
|
||||
url += tag
|
||||
|
||||
activity?.toast(R.string.msg_downloading_content)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val content = Utils.getUrlContext(url, 5000)
|
||||
launch(Dispatchers.Main) {
|
||||
val routingList = if (TextUtils.isEmpty(content)) {
|
||||
Utils.readTextFromAssets(activity?.v2RayApplication, "custom_routing_$tag")
|
||||
} else {
|
||||
content
|
||||
}
|
||||
binding.etRoutingContent.text = Utils.getEditable(routingList)
|
||||
saveRouting()
|
||||
//toast(R.string.toast_success)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,10 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import io.github.g00fy2.quickie.QRResult
|
||||
import io.github.g00fy2.quickie.ScanCustomCode
|
||||
@@ -22,7 +21,6 @@ import io.github.g00fy2.quickie.config.ScannerConfig
|
||||
class ScannerActivity : BaseActivity() {
|
||||
|
||||
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
@@ -27,25 +26,17 @@ import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
||||
import com.v2ray.ang.extension.removeWhiteSpace
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.Utils.getIpv6Address
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SETTING,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||
private val isRunning by lazy {
|
||||
intent.getBooleanExtra("isRunning", false)
|
||||
&& editGuid.isNotEmpty()
|
||||
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
|
||||
&& editGuid == MmkvManager.getSelectServer()
|
||||
}
|
||||
private val createConfigType by lazy {
|
||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value))
|
||||
@@ -102,11 +93,11 @@ class ServerActivity : BaseActivity() {
|
||||
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
|
||||
private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) }
|
||||
private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) }
|
||||
private val container_allow_insecure: LinearLayout? by lazy { findViewById(R.id.l5) }
|
||||
private val container_allow_insecure: LinearLayout? by lazy { findViewById(R.id.lay_allow_insecure) }
|
||||
private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) }
|
||||
private val container_sni: LinearLayout? by lazy { findViewById(R.id.l2) }
|
||||
private val container_sni: LinearLayout? by lazy { findViewById(R.id.lay_sni) }
|
||||
private val sp_stream_fingerprint: Spinner? by lazy { findViewById(R.id.sp_stream_fingerprint) } //uTLS
|
||||
private val container_fingerprint: LinearLayout? by lazy { findViewById(R.id.l3) }
|
||||
private val container_fingerprint: LinearLayout? by lazy { findViewById(R.id.lay_stream_fingerprint) }
|
||||
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_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
||||
@@ -115,18 +106,19 @@ class ServerActivity : BaseActivity() {
|
||||
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 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.lay_stream_alpn) }
|
||||
private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) }
|
||||
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.l6) }
|
||||
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.lay_public_key) }
|
||||
private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) }
|
||||
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) }
|
||||
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
|
||||
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
|
||||
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) }
|
||||
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) }
|
||||
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
|
||||
private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) }
|
||||
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
|
||||
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
|
||||
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
|
||||
private val et_obfs_password: EditText? by lazy { findViewById(R.id.et_obfs_password) }
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -139,9 +131,11 @@ class ServerActivity : BaseActivity() {
|
||||
EConfigType.CUSTOM -> return
|
||||
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||
EConfigType.HTTP -> setContentView(R.layout.activity_server_socks)
|
||||
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
|
||||
EConfigType.HYSTERIA2 -> setContentView(R.layout.activity_server_hysteria2)
|
||||
}
|
||||
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(
|
||||
@@ -258,7 +252,9 @@ class ServerActivity : BaseActivity() {
|
||||
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
||||
et_alterId?.text =
|
||||
Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
||||
if (config.configType == EConfigType.SOCKS) {
|
||||
if (config.configType == EConfigType.SOCKS
|
||||
|| config.configType == EConfigType.HTTP
|
||||
) {
|
||||
et_security?.text =
|
||||
Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
@@ -290,14 +286,17 @@ class ServerActivity : BaseActivity() {
|
||||
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
|
||||
} else {
|
||||
val list = outbound.settings?.address as List<*>
|
||||
et_local_address?.text = Utils.getEditable(list.joinToString())
|
||||
et_local_address?.text = Utils.getEditable(list.joinToString(","))
|
||||
}
|
||||
if (outbound.settings?.mtu == null) {
|
||||
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
||||
} else {
|
||||
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
|
||||
}
|
||||
} else if (config.configType == EConfigType.HYSTERIA2) {
|
||||
et_obfs_password?.text = Utils.getEditable(outbound.settings?.obfsPassword)
|
||||
}
|
||||
|
||||
val securityEncryptions =
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||
val security =
|
||||
@@ -322,7 +321,7 @@ class ServerActivity : BaseActivity() {
|
||||
tlsSetting.alpn?.let {
|
||||
val alpnIndex = Utils.arrayFind(
|
||||
alpns,
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
|
||||
)
|
||||
sp_stream_alpn?.setSelection(alpnIndex)
|
||||
}
|
||||
@@ -413,8 +412,14 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
val config =
|
||||
MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
|
||||
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
|
||||
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
|
||||
if (config.configType != EConfigType.SOCKS
|
||||
&& config.configType != EConfigType.HTTP
|
||||
&& TextUtils.isEmpty(et_id.text.toString())
|
||||
) {
|
||||
if (config.configType == EConfigType.TROJAN
|
||||
|| config.configType == EConfigType.SHADOWSOCKS
|
||||
|| config.configType == EConfigType.HYSTERIA2
|
||||
) {
|
||||
toast(R.string.server_lab_id3)
|
||||
} else {
|
||||
toast(R.string.server_lab_id)
|
||||
@@ -446,12 +451,17 @@ class ServerActivity : BaseActivity() {
|
||||
wireguard?.peers?.get(0)?.let { _ ->
|
||||
savePeer(wireguard, port)
|
||||
}
|
||||
|
||||
config.outboundBean?.streamSettings?.let {
|
||||
saveStreamSettings(it)
|
||||
val sni = saveStreamSettings(it)
|
||||
saveTls(it, sni)
|
||||
}
|
||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
config.subscriptionId = subscriptionId.orEmpty()
|
||||
}
|
||||
if (config.configType == EConfigType.HYSTERIA2) {
|
||||
config.outboundBean?.settings?.obfsPassword = et_obfs_password?.text?.toString()
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
toast(R.string.toast_success)
|
||||
@@ -486,7 +496,7 @@ class ServerActivity : BaseActivity() {
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||
server.password = et_id.text.toString().trim()
|
||||
server.method = shadowsocksSecuritys[sp_security?.selectedItemPosition ?: 0]
|
||||
} else if (config.configType == EConfigType.SOCKS) {
|
||||
} else if (config.configType == EConfigType.SOCKS || config.configType == EConfigType.HTTP) {
|
||||
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
|
||||
server.users = null
|
||||
} else {
|
||||
@@ -496,7 +506,7 @@ class ServerActivity : BaseActivity() {
|
||||
socksUsersBean.pass = et_id.text.toString().trim()
|
||||
server.users = listOf(socksUsersBean)
|
||||
}
|
||||
} else if (config.configType == EConfigType.TROJAN) {
|
||||
} else if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.HYSTERIA2) {
|
||||
server.password = et_id.text.toString().trim()
|
||||
}
|
||||
}
|
||||
@@ -518,21 +528,13 @@ class ServerActivity : BaseActivity() {
|
||||
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
|
||||
}
|
||||
|
||||
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
|
||||
val network = sp_network?.selectedItemPosition ?: return
|
||||
val type = sp_header_type?.selectedItemPosition ?: return
|
||||
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
|
||||
val path = et_path?.text?.toString()?.trim() ?: return
|
||||
val sniField = et_sni?.text?.toString()?.trim() ?: return
|
||||
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
|
||||
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
|
||||
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
|
||||
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
|
||||
val publicKey = et_public_key?.text?.toString()?.trim() ?: return
|
||||
val shortId = et_short_id?.text?.toString()?.trim() ?: return
|
||||
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
|
||||
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): String? {
|
||||
val network = sp_network?.selectedItemPosition ?: return null
|
||||
val type = sp_header_type?.selectedItemPosition ?: return null
|
||||
val requestHost = et_request_host?.text?.toString()?.trim() ?: return null
|
||||
val path = et_path?.text?.toString()?.trim() ?: return null
|
||||
|
||||
var sni = streamSetting.populateTransportSettings(
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
transport = networks[network],
|
||||
headerType = transportTypes(networks[network])[type],
|
||||
host = requestHost,
|
||||
@@ -544,10 +546,21 @@ class ServerActivity : BaseActivity() {
|
||||
serviceName = path,
|
||||
authority = requestHost,
|
||||
)
|
||||
if (sniField.isNotBlank()) {
|
||||
sni = sniField
|
||||
}
|
||||
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
|
||||
|
||||
return sni
|
||||
}
|
||||
|
||||
private fun saveTls(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, sni: String?) {
|
||||
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
|
||||
val sniField = et_sni?.text?.toString()?.trim()
|
||||
val allowInsecureField = sp_allow_insecure?.selectedItemPosition
|
||||
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: 0
|
||||
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: 0
|
||||
val publicKey = et_public_key?.text?.toString()
|
||||
val shortId = et_short_id?.text?.toString()
|
||||
val spiderX = et_spider_x?.text?.toString()
|
||||
|
||||
val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
|
||||
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
||||
} else {
|
||||
allowinsecures[allowInsecureField].toBoolean()
|
||||
@@ -556,7 +569,7 @@ class ServerActivity : BaseActivity() {
|
||||
streamSetting.populateTlsSettings(
|
||||
streamSecurity = streamSecuritys[streamSecurity],
|
||||
allowInsecure = allowInsecure,
|
||||
sni = sni,
|
||||
sni = sniField ?: sni ?: "",
|
||||
fingerprint = uTlsItems[utlsIndex],
|
||||
alpns = alpns[alpnIndex],
|
||||
publicKey = publicKey,
|
||||
@@ -590,7 +603,7 @@ class ServerActivity : BaseActivity() {
|
||||
*/
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
if (editGuid != mainStorage?.decodeString(KEY_SELECTED_SERVER)) {
|
||||
if (editGuid != MmkvManager.getSelectServer()) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
|
||||
@@ -8,14 +8,14 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
@@ -23,13 +23,11 @@ import me.drakeet.support.toast.ToastCompat
|
||||
class ServerCustomConfigActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivityServerCustomConfigBinding.inflate(layoutInflater) }
|
||||
|
||||
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 editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||
private val isRunning by lazy {
|
||||
intent.getBooleanExtra("isRunning", false)
|
||||
&& editGuid.isNotEmpty()
|
||||
&& editGuid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
&& editGuid == MmkvManager.getSelectServer()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -54,7 +52,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
*/
|
||||
private fun bindingServer(config: ServerConfig): Boolean {
|
||||
binding.etRemarks.text = Utils.getEditable(config.remarks)
|
||||
val raw = serverRawStorage?.decodeString(editGuid)
|
||||
val raw = MmkvManager.decodeServerRaw(editGuid)
|
||||
if (raw.isNullOrBlank()) {
|
||||
binding.editor.setTextContent(Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty()))
|
||||
} else {
|
||||
@@ -81,7 +79,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val v2rayConfig = try {
|
||||
Gson().fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
|
||||
JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||
@@ -93,7 +91,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
config.fullConfig = v2rayConfig
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
serverRawStorage?.encode(editGuid, binding.editor.text.toString())
|
||||
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
|
||||
@@ -8,17 +8,17 @@ import androidx.activity.viewModels
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.multiprocess.RemoteWorkManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.VPN
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toLongEx
|
||||
import com.v2ray.ang.service.SubscriptionUpdater
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -36,7 +36,6 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
|
||||
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
||||
@@ -44,8 +43,6 @@ class SettingsActivity : BaseActivity() {
|
||||
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
||||
|
||||
private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
|
||||
|
||||
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
||||
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
||||
private val muxXudpConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_XUDP_CONCURRENCY) }
|
||||
@@ -89,11 +86,6 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
routingCustom?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
||||
false
|
||||
}
|
||||
|
||||
mux?.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateMux(newValue as Boolean)
|
||||
true
|
||||
@@ -128,7 +120,7 @@ class SettingsActivity : BaseActivity() {
|
||||
val value = newValue as Boolean
|
||||
autoUpdateCheck?.isChecked = value
|
||||
autoUpdateInterval?.isEnabled = value
|
||||
autoUpdateInterval?.text?.toLong()?.let {
|
||||
autoUpdateInterval?.text?.toLongEx()?.let {
|
||||
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
|
||||
}
|
||||
true
|
||||
@@ -138,9 +130,9 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
|
||||
nval =
|
||||
if (TextUtils.isEmpty(nval) || nval.toLong() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
|
||||
if (TextUtils.isEmpty(nval) || nval.toLongEx() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
|
||||
autoUpdateInterval?.summary = nval
|
||||
configureUpdateTask(nval.toLong())
|
||||
configureUpdateTask(nval.toLongEx())
|
||||
true
|
||||
}
|
||||
|
||||
@@ -180,7 +172,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, "VPN"))
|
||||
updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, VPN))
|
||||
localDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
||||
fakeDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FAKE_DNS_ENABLED, false)
|
||||
localDnsPort?.summary = settingsStorage.decodeString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
|
||||
@@ -238,6 +230,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
listOf(
|
||||
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||
AppConfig.PREF_IS_BOOTED,
|
||||
AppConfig.PREF_BYPASS_APPS,
|
||||
AppConfig.PREF_SPEED_ENABLED,
|
||||
AppConfig.PREF_CONFIRM_REMOVE,
|
||||
@@ -252,7 +245,6 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
listOf(
|
||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||
AppConfig.PREF_ROUTING_MODE,
|
||||
AppConfig.PREF_MUX_XUDP_QUIC,
|
||||
AppConfig.PREF_FRAGMENT_PACKETS,
|
||||
AppConfig.PREF_LANGUAGE,
|
||||
@@ -267,7 +259,7 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun updateMode(mode: String?) {
|
||||
val vpn = mode == "VPN"
|
||||
val vpn = mode == VPN
|
||||
perAppProxy?.isEnabled = vpn
|
||||
perAppProxy?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
localDns?.isEnabled = vpn
|
||||
|
||||
@@ -6,8 +6,6 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
@@ -23,7 +21,6 @@ class SubEditActivity : BaseActivity() {
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -31,9 +28,9 @@ class SubEditActivity : BaseActivity() {
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
|
||||
val subItem = MmkvManager.decodeSubscription(editSubId)
|
||||
if (subItem != null) {
|
||||
bindingServer(subItem)
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
@@ -45,8 +42,11 @@ class SubEditActivity : BaseActivity() {
|
||||
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
||||
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
|
||||
binding.etUrl.text = Utils.getEditable(subItem.url)
|
||||
binding.etFilter.text = Utils.getEditable(subItem.filter)
|
||||
binding.chkEnable.isChecked = subItem.enabled
|
||||
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
|
||||
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
|
||||
binding.etNextProfile.text = Utils.getEditable(subItem.nextProfile)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -56,7 +56,10 @@ class SubEditActivity : BaseActivity() {
|
||||
private fun clearServer(): Boolean {
|
||||
binding.etRemarks.text = null
|
||||
binding.etUrl.text = null
|
||||
binding.etFilter.text = null
|
||||
binding.chkEnable.isChecked = true
|
||||
binding.etPreProfile.text = null
|
||||
binding.etNextProfile.text = null
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -64,20 +67,15 @@ class SubEditActivity : BaseActivity() {
|
||||
* save server config
|
||||
*/
|
||||
private fun saveServer(): Boolean {
|
||||
val subItem: SubscriptionItem
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
var subId = editSubId
|
||||
if (!json.isNullOrBlank()) {
|
||||
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
} else {
|
||||
subId = Utils.getUuid()
|
||||
subItem = SubscriptionItem()
|
||||
}
|
||||
val subItem = MmkvManager.decodeSubscription(editSubId) ?: SubscriptionItem()
|
||||
|
||||
subItem.remarks = binding.etRemarks.text.toString()
|
||||
subItem.url = binding.etUrl.text.toString()
|
||||
subItem.filter = binding.etFilter.text.toString()
|
||||
subItem.enabled = binding.chkEnable.isChecked
|
||||
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
|
||||
subItem.prevProfile = binding.etPreProfile.text.toString()
|
||||
subItem.nextProfile = binding.etNextProfile.text.toString()
|
||||
|
||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||
toast(R.string.sub_setting_remarks)
|
||||
@@ -88,7 +86,7 @@ class SubEditActivity : BaseActivity() {
|
||||
// return false
|
||||
// }
|
||||
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
MmkvManager.encodeSubscription(editSubId, subItem)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
|
||||
@@ -6,12 +6,14 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -23,6 +25,7 @@ class SubSettingActivity : BaseActivity() {
|
||||
|
||||
var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
|
||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -33,12 +36,14 @@ class SubSettingActivity : BaseActivity() {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
|
||||
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
subscriptions = MmkvManager.decodeSubscriptions()
|
||||
adapter.notifyDataSetChanged()
|
||||
refreshData()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -77,4 +82,9 @@ class SubSettingActivity : BaseActivity() {
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
}
|
||||
|
||||
fun refreshData() {
|
||||
subscriptions = MmkvManager.decodeSubscriptions()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,20 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
|
||||
|
||||
private var mActivity: SubSettingActivity = activity
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private val share_method: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_sub_method)
|
||||
@@ -35,11 +34,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
val subItem = mActivity.subscriptions[position].second
|
||||
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
||||
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
||||
if (subItem.enabled) {
|
||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorAccent)
|
||||
} else {
|
||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(0)
|
||||
}
|
||||
holder.itemSubSettingBinding.chkEnable.isChecked = subItem.enabled
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
||||
@@ -48,10 +43,12 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
.putExtra("subId", subId)
|
||||
)
|
||||
}
|
||||
holder.itemSubSettingBinding.infoContainer.setOnClickListener {
|
||||
subItem.enabled = !subItem.enabled
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
notifyItemChanged(position)
|
||||
|
||||
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
||||
if( !it.isPressed) return@setOnCheckedChangeListener
|
||||
subItem.enabled = isChecked
|
||||
MmkvManager.encodeSubscription(subId, subItem)
|
||||
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(subItem.url)) {
|
||||
@@ -99,5 +96,28 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
}
|
||||
|
||||
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) :
|
||||
RecyclerView.ViewHolder(itemSubSettingBinding.root)
|
||||
BaseViewHolder(itemSubSettingBinding.root), ItemTouchHelperViewHolder
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
fun onItemSelected() {
|
||||
itemView.setBackgroundColor(Color.LTGRAY)
|
||||
}
|
||||
|
||||
fun onItemClear() {
|
||||
itemView.setBackgroundColor(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
SettingsManager.swapSubscriptions(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
mActivity.refreshData()
|
||||
}
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
||||
@@ -21,8 +20,6 @@ class TaskerActivity : BaseActivity() {
|
||||
private var lstData: ArrayList<String> = ArrayList()
|
||||
private var lstGuid: ArrayList<String> = ArrayList()
|
||||
|
||||
private val serverStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
@@ -31,7 +28,7 @@ class TaskerActivity : BaseActivity() {
|
||||
lstData.add("Default")
|
||||
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
|
||||
|
||||
serverStorage?.allKeys()?.forEach { key ->
|
||||
MmkvManager.decodeServerList()?.forEach { key ->
|
||||
MmkvManager.decodeServerConfig(key)?.let { config ->
|
||||
lstData.add(config.remarks)
|
||||
lstGuid.add(key)
|
||||
|
||||
@@ -19,10 +19,9 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
||||
@@ -31,6 +30,8 @@ import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toTrafficString
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -45,8 +46,6 @@ import java.util.Date
|
||||
|
||||
class UserAssetActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||
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) }
|
||||
|
||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
|
||||
@@ -138,7 +137,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
toast(R.string.msg_remark_is_duplicate)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
assetStorage?.encode(assetId, Gson().toJson(assetItem))
|
||||
MmkvManager.encodeAsset(assetId, assetItem)
|
||||
copyFile(uri)
|
||||
} catch (e: Exception) {
|
||||
toast(R.string.toast_asset_copy_failed)
|
||||
@@ -178,7 +177,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
.show()
|
||||
toast(R.string.msg_downloading_content)
|
||||
|
||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
|
||||
@@ -215,7 +214,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
URL(item.url).openConnection(
|
||||
Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", httpPort)
|
||||
InetSocketAddress(LOOPBACK, httpPort)
|
||||
)
|
||||
) as HttpURLConnection
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
@@ -22,7 +20,6 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val editAssetId by lazy { intent.getStringExtra("assetId").orEmpty() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -30,9 +27,9 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_user_asset_add_url)
|
||||
|
||||
val json = assetStorage?.decodeString(editAssetId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
bindingAsset(Gson().fromJson(json, AssetUrlItem::class.java))
|
||||
val assetItem = MmkvManager.decodeAsset(editAssetId)
|
||||
if (assetItem != null) {
|
||||
bindingAsset(assetItem)
|
||||
} else {
|
||||
clearAsset()
|
||||
}
|
||||
@@ -60,12 +57,9 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
* save asset config
|
||||
*/
|
||||
private fun saveServer(): Boolean {
|
||||
val assetItem: AssetUrlItem
|
||||
val json = assetStorage?.decodeString(editAssetId)
|
||||
var assetItem = MmkvManager.decodeAsset(editAssetId)
|
||||
var assetId = editAssetId
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetItem = Gson().fromJson(json, AssetUrlItem::class.java)
|
||||
|
||||
if (assetItem != null) {
|
||||
// remove file associated with the asset
|
||||
val file = extDir.resolve(assetItem.remarks)
|
||||
if (file.exists()) {
|
||||
@@ -96,7 +90,7 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
assetStorage?.encode(assetId, Gson().toJson(assetItem))
|
||||
MmkvManager.encodeAsset(assetId, assetItem)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
|
||||
@@ -4,17 +4,17 @@ import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.HY2
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.*
|
||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import com.v2ray.ang.util.fmt.Hysteria2Fmt
|
||||
import com.v2ray.ang.util.fmt.ShadowsocksFmt
|
||||
import com.v2ray.ang.util.fmt.SocksFmt
|
||||
import com.v2ray.ang.util.fmt.TrojanFmt
|
||||
@@ -22,191 +22,17 @@ 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.net.URI
|
||||
import java.util.*
|
||||
|
||||
object AngConfigManager {
|
||||
private val mainStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_MAIN,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val serverRawStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SERVER_RAW,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val settingsStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SETTING,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
/**
|
||||
* Legacy loading config
|
||||
*/
|
||||
// fun migrateLegacyConfig(c: Context): Boolean? {
|
||||
// try {
|
||||
// val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
|
||||
// val context = defaultSharedPreferences.getString(ANG_CONFIG, "")
|
||||
// if (context.isNullOrBlank()) {
|
||||
// return null
|
||||
// }
|
||||
// val angConfig = Gson().fromJson(context, AngConfig::class.java)
|
||||
// for (i in angConfig.vmess.indices) {
|
||||
// upgradeServerVersion(angConfig.vmess[i])
|
||||
// }
|
||||
//
|
||||
// copyLegacySettings(defaultSharedPreferences)
|
||||
// migrateVmessBean(angConfig, defaultSharedPreferences)
|
||||
// migrateSubItemBean(angConfig)
|
||||
//
|
||||
// defaultSharedPreferences.edit().remove(ANG_CONFIG).apply()
|
||||
// return true
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// private fun copyLegacySettings(sharedPreferences: SharedPreferences) {
|
||||
// listOf(
|
||||
// AppConfig.PREF_MODE,
|
||||
// AppConfig.PREF_REMOTE_DNS,
|
||||
// AppConfig.PREF_DOMESTIC_DNS,
|
||||
// AppConfig.PREF_LOCAL_DNS_PORT,
|
||||
// AppConfig.PREF_SOCKS_PORT,
|
||||
// AppConfig.PREF_HTTP_PORT,
|
||||
// AppConfig.PREF_LOGLEVEL,
|
||||
// AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||
// AppConfig.PREF_ROUTING_MODE,
|
||||
// AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||
// AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
||||
// AppConfig.PREF_V2RAY_ROUTING_DIRECT,
|
||||
// ).forEach { key ->
|
||||
// settingsStorage?.encode(key, sharedPreferences.getString(key, null))
|
||||
// }
|
||||
// listOf(
|
||||
// AppConfig.PREF_SPEED_ENABLED,
|
||||
// AppConfig.PREF_PROXY_SHARING,
|
||||
// AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||
// AppConfig.PREF_ALLOW_INSECURE,
|
||||
// AppConfig.PREF_PREFER_IPV6,
|
||||
// AppConfig.PREF_PER_APP_PROXY,
|
||||
// AppConfig.PREF_BYPASS_APPS,
|
||||
// ).forEach { key ->
|
||||
// settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||
// }
|
||||
// settingsStorage?.encode(
|
||||
// AppConfig.PREF_SNIFFING_ENABLED,
|
||||
// sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||
// )
|
||||
// settingsStorage?.encode(
|
||||
// AppConfig.PREF_PER_APP_PROXY_SET,
|
||||
// sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
|
||||
// angConfig.vmess.forEachIndexed { index, vmessBean ->
|
||||
// val type = EConfigType.fromInt(vmessBean.configType) ?: return@forEachIndexed
|
||||
// val config = ServerConfig.create(type)
|
||||
// config.remarks = vmessBean.remarks
|
||||
// config.subscriptionId = vmessBean.subid
|
||||
// if (type == EConfigType.CUSTOM) {
|
||||
// val jsonConfig = sharedPreferences.getString(ANG_CONFIG + vmessBean.guid, "")
|
||||
// val v2rayConfig = try {
|
||||
// Gson().fromJson(jsonConfig, V2rayConfig::class.java)
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// return@forEachIndexed
|
||||
// }
|
||||
// config.fullConfig = v2rayConfig
|
||||
// serverRawStorage?.encode(vmessBean.guid, jsonConfig)
|
||||
// } else {
|
||||
// config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
// vnext.address = vmessBean.address
|
||||
// vnext.port = vmessBean.port
|
||||
// vnext.users[0].id = vmessBean.id
|
||||
// if (config.configType == EConfigType.VMESS) {
|
||||
// vnext.users[0].alterId = vmessBean.alterId
|
||||
// vnext.users[0].security = vmessBean.security
|
||||
// } else if (config.configType == EConfigType.VLESS) {
|
||||
// vnext.users[0].encryption = vmessBean.security
|
||||
// vnext.users[0].flow = vmessBean.flow
|
||||
// }
|
||||
// }
|
||||
// config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
// server.address = vmessBean.address
|
||||
// server.port = vmessBean.port
|
||||
// if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||
// server.password = vmessBean.id
|
||||
// server.method = vmessBean.security
|
||||
// } else if (config.configType == EConfigType.SOCKS) {
|
||||
// if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
|
||||
// server.users = null
|
||||
// } else {
|
||||
// val socksUsersBean =
|
||||
// V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
// socksUsersBean.user = vmessBean.security
|
||||
// socksUsersBean.pass = vmessBean.id
|
||||
// server.users = listOf(socksUsersBean)
|
||||
// }
|
||||
// } else if (config.configType == EConfigType.TROJAN) {
|
||||
// server.password = vmessBean.id
|
||||
// }
|
||||
// }
|
||||
// config.outboundBean?.streamSettings?.let { streamSetting ->
|
||||
// val sni = streamSetting.populateTransportSettings(
|
||||
// vmessBean.network,
|
||||
// vmessBean.headerType,
|
||||
// vmessBean.requestHost,
|
||||
// vmessBean.path,
|
||||
// vmessBean.path,
|
||||
// vmessBean.requestHost,
|
||||
// vmessBean.path,
|
||||
// vmessBean.headerType,
|
||||
// vmessBean.path,
|
||||
// vmessBean.requestHost,
|
||||
// )
|
||||
// val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
|
||||
// settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
// } else {
|
||||
// vmessBean.allowInsecure.toBoolean()
|
||||
// }
|
||||
// var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||
// streamSetting.populateTlsSettings(
|
||||
// vmessBean.streamSecurity, allowInsecure,
|
||||
// vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
|
||||
// if (index == angConfig.index) {
|
||||
// mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun migrateSubItemBean(angConfig: AngConfig) {
|
||||
// angConfig.subItem.forEach {
|
||||
// val subItem = SubscriptionItem()
|
||||
// subItem.remarks = it.remarks
|
||||
// subItem.url = it.url
|
||||
// subItem.enabled = it.enabled
|
||||
// subStorage?.encode(it.id, Gson().toJson(subItem))
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* parse config form qrcode or...
|
||||
*/
|
||||
private fun parseConfig(
|
||||
str: String?,
|
||||
subid: String,
|
||||
subItem: SubscriptionItem?,
|
||||
removedSelectedServer: ServerConfig?
|
||||
): Int {
|
||||
try {
|
||||
@@ -215,17 +41,19 @@ object AngConfigManager {
|
||||
}
|
||||
|
||||
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
||||
VmessFmt.parseVmess(str)
|
||||
VmessFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||
ShadowsocksFmt.parseShadowsocks(str)
|
||||
ShadowsocksFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
||||
SocksFmt.parseSocks(str)
|
||||
SocksFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
||||
TrojanFmt.parseTrojan(str)
|
||||
TrojanFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
||||
VlessFmt.parseVless(str)
|
||||
VlessFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||
WireguardFmt.parseWireguard(str)
|
||||
WireguardFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme) || str.startsWith(HY2)) {
|
||||
Hysteria2Fmt.parse(str)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -233,6 +61,13 @@ object AngConfigManager {
|
||||
if (config == null) {
|
||||
return R.string.toast_incorrect_protocol
|
||||
}
|
||||
//filter
|
||||
if (subItem?.filter != null && subItem.filter?.isNotEmpty() == true && config.remarks.isNotEmpty()) {
|
||||
val matched = Regex(pattern = subItem.filter ?: "")
|
||||
.containsMatchIn(input = config.remarks)
|
||||
if (!matched) return -1
|
||||
}
|
||||
|
||||
config.subscriptionId = subid
|
||||
val guid = MmkvManager.encodeServerConfig("", config)
|
||||
if (removedSelectedServer != null &&
|
||||
@@ -243,7 +78,7 @@ object AngConfigManager {
|
||||
?.getServerPort() == removedSelectedServer.getProxyOutbound()
|
||||
?.getServerPort()
|
||||
) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
|
||||
MmkvManager.setSelectServer(guid)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -264,9 +99,11 @@ object AngConfigManager {
|
||||
EConfigType.CUSTOM -> ""
|
||||
EConfigType.SHADOWSOCKS -> ShadowsocksFmt.toUri(config)
|
||||
EConfigType.SOCKS -> SocksFmt.toUri(config)
|
||||
EConfigType.HTTP -> ""
|
||||
EConfigType.VLESS -> VlessFmt.toUri(config)
|
||||
EConfigType.TROJAN -> TrojanFmt.toUri(config)
|
||||
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
|
||||
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -353,39 +190,6 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
|
||||
// /**
|
||||
// * upgrade
|
||||
// */
|
||||
// private fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int {
|
||||
// try {
|
||||
// if (vmess.configVersion == 2) {
|
||||
// return 0
|
||||
// }
|
||||
//
|
||||
// when (vmess.network) {
|
||||
// "ws", "h2" -> {
|
||||
// var path = ""
|
||||
// var host = ""
|
||||
// val lstParameter = vmess.requestHost.split(";")
|
||||
// if (lstParameter.isNotEmpty()) {
|
||||
// path = lstParameter[0].trim()
|
||||
// }
|
||||
// if (lstParameter.size > 1) {
|
||||
// path = lstParameter[0].trim()
|
||||
// host = lstParameter[1].trim()
|
||||
// }
|
||||
// vmess.path = path
|
||||
// vmess.requestHost = host
|
||||
// }
|
||||
// }
|
||||
// vmess.configVersion = 2
|
||||
// return 0
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// return -1
|
||||
// }
|
||||
// }
|
||||
|
||||
fun importBatchConfig(server: String?, subid: String, append: Boolean): Pair<Int, Int> {
|
||||
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||
if (count <= 0) {
|
||||
@@ -416,7 +220,7 @@ object AngConfigManager {
|
||||
servers.lines()
|
||||
.forEach { str ->
|
||||
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
|
||||
count += MmkvManager.importUrlAsSubscription(str)
|
||||
count += importUrlAsSubscription(str)
|
||||
}
|
||||
}
|
||||
return count
|
||||
@@ -434,7 +238,7 @@ object AngConfigManager {
|
||||
val removedSelectedServer =
|
||||
if (!TextUtils.isEmpty(subid) && !append) {
|
||||
MmkvManager.decodeServerConfig(
|
||||
mainStorage?.decodeString(KEY_SELECTED_SERVER).orEmpty()
|
||||
MmkvManager.getSelectServer().orEmpty()
|
||||
)?.let {
|
||||
if (it.subscriptionId == subid) {
|
||||
return@let it
|
||||
@@ -448,11 +252,12 @@ object AngConfigManager {
|
||||
MmkvManager.removeServerViaSubid(subid)
|
||||
}
|
||||
|
||||
val subItem = MmkvManager.decodeSubscription(subid)
|
||||
var count = 0
|
||||
servers.lines()
|
||||
.reversed()
|
||||
.forEach {
|
||||
val resId = parseConfig(it, subid, removedSelectedServer)
|
||||
val resId = parseConfig(it, subid, subItem, removedSelectedServer)
|
||||
if (resId == 0) {
|
||||
count++
|
||||
}
|
||||
@@ -473,34 +278,21 @@ object AngConfigManager {
|
||||
&& server.contains("routing")
|
||||
) {
|
||||
try {
|
||||
//val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
|
||||
object : TypeToken<Double>() {}.type,
|
||||
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
|
||||
JsonPrimitive(
|
||||
src?.toInt()
|
||||
)
|
||||
}
|
||||
)
|
||||
.create()
|
||||
val serverList: Array<Any> =
|
||||
Gson().fromJson(server, Array<Any>::class.java)
|
||||
JsonUtil.fromJson(server, Array<Any>::class.java)
|
||||
|
||||
if (serverList.isNotEmpty()) {
|
||||
var count = 0
|
||||
for (srv in serverList.reversed()) {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.fullConfig =
|
||||
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
||||
JsonUtil.fromJson(JsonUtil.toJson(srv), V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks
|
||||
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
||||
.toString())
|
||||
config.subscriptionId = subid
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, gson.toJson(srv))
|
||||
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
@@ -509,21 +301,31 @@ object AngConfigManager {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
// For compatibility
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.subscriptionId = subid
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, server)
|
||||
return 1
|
||||
try {
|
||||
// For compatibility
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.subscriptionId = subid
|
||||
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
return 1
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return 0
|
||||
} 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
|
||||
try {
|
||||
val config = WireguardFmt.parseWireguardConfFile(server)
|
||||
?: return R.string.toast_incorrect_protocol
|
||||
config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
return 1
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
@@ -558,19 +360,17 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
Log.d(AppConfig.ANG_PACKAGE, url)
|
||||
|
||||
var configText = try {
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
|
||||
} 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)
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
@@ -596,4 +396,19 @@ object AngConfigManager {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun importUrlAsSubscription(url: String): Int {
|
||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||
subscriptions.forEach {
|
||||
if (it.second.url == url) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
val uri = URI(Utils.fixIllegalUrl(url))
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = uri.fragment ?: "import sub"
|
||||
subItem.url = url
|
||||
MmkvManager.encodeSubscription("", subItem)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
|
||||
object AppManagerUtil {
|
||||
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
||||
private fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
||||
val packageManager = ctx.packageManager
|
||||
val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
|
||||
val apps = ArrayList<AppInfo>()
|
||||
|
||||
for (pkg in packages) {
|
||||
if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
|
||||
//if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
|
||||
|
||||
val applicationInfo = pkg.applicationInfo
|
||||
|
||||
@@ -35,9 +33,9 @@ object AppManagerUtil {
|
||||
it.onNext(loadNetworkAppList(ctx))
|
||||
}
|
||||
|
||||
val PackageInfo.hasInternetPermission: Boolean
|
||||
get() {
|
||||
val permissions = requestedPermissions
|
||||
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
||||
}
|
||||
// val PackageInfo.hasInternetPermission: Boolean
|
||||
// get() {
|
||||
// val permissions = requestedPermissions
|
||||
// return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
||||
// }
|
||||
}
|
||||
|
||||
37
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/JsonUtil.kt
Normal file
37
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/JsonUtil.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.lang.reflect.Type
|
||||
|
||||
object JsonUtil {
|
||||
private var gson = Gson()
|
||||
|
||||
fun toJson(src: Any?): String {
|
||||
return gson.toJson(src)
|
||||
}
|
||||
|
||||
fun <T> fromJson(json: String, cls: Class<T>): T {
|
||||
return gson.fromJson(json, cls)
|
||||
}
|
||||
|
||||
fun toJsonPretty(src: Any?): String {
|
||||
val gsonPre = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
|
||||
object : TypeToken<Double>() {}.type,
|
||||
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
|
||||
JsonPrimitive(
|
||||
src?.toInt()
|
||||
)
|
||||
}
|
||||
)
|
||||
.create()
|
||||
return gsonPre.toJson(src)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,63 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig.PREF_IS_BOOTED
|
||||
import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import java.net.URI
|
||||
|
||||
object MmkvManager {
|
||||
const val ID_MAIN = "MAIN"
|
||||
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_AFF = "SERVER_AFF"
|
||||
const val ID_SUB = "SUB"
|
||||
const val ID_ASSET = "ASSET"
|
||||
const val ID_SETTING = "SETTING"
|
||||
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||
|
||||
val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
//region private
|
||||
|
||||
private const val ID_MAIN = "MAIN"
|
||||
private const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
||||
private const val ID_PROFILE_CONFIG = "PROFILE_CONFIG"
|
||||
private const val ID_SERVER_RAW = "SERVER_RAW"
|
||||
private const val ID_SERVER_AFF = "SERVER_AFF"
|
||||
private const val ID_SUB = "SUB"
|
||||
private const val ID_ASSET = "ASSET"
|
||||
private const val ID_SETTING = "SETTING"
|
||||
private const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||
private const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||
private const val KEY_SUB_IDS = "SUB_IDS"
|
||||
|
||||
private 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 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) }
|
||||
val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private 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) }
|
||||
val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
//endregion
|
||||
|
||||
//region Server
|
||||
|
||||
fun getSelectServer(): String? {
|
||||
return mainStorage.decodeString(KEY_SELECTED_SERVER)
|
||||
}
|
||||
|
||||
fun setSelectServer(guid: String) {
|
||||
mainStorage.encode(KEY_SELECTED_SERVER, guid)
|
||||
}
|
||||
|
||||
fun encodeServerList(serverList: MutableList<String>) {
|
||||
mainStorage.encode(KEY_ANG_CONFIGS, JsonUtil.toJson(serverList))
|
||||
}
|
||||
|
||||
fun decodeServerList(): MutableList<String> {
|
||||
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||
val json = mainStorage.decodeString(KEY_ANG_CONFIGS)
|
||||
return if (json.isNullOrBlank()) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
Gson().fromJson(json, Array<String>::class.java).toMutableList()
|
||||
JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,33 +65,33 @@ object MmkvManager {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = serverStorage?.decodeString(guid)
|
||||
val json = serverStorage.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerConfig::class.java)
|
||||
return JsonUtil.fromJson(json, ServerConfig::class.java)
|
||||
}
|
||||
|
||||
fun decodeProfileConfig(guid: String): ProfileItem? {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = profileStorage?.decodeString(guid)
|
||||
val json = profileStorage.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ProfileItem::class.java)
|
||||
return JsonUtil.fromJson(json, ProfileItem::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||
val key = guid.ifBlank { Utils.getUuid() }
|
||||
serverStorage?.encode(key, Gson().toJson(config))
|
||||
serverStorage.encode(key, JsonUtil.toJson(config))
|
||||
val serverList = decodeServerList()
|
||||
if (!serverList.contains(key)) {
|
||||
serverList.add(0, key)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||
encodeServerList(serverList)
|
||||
if (getSelectServer().isNullOrBlank()) {
|
||||
mainStorage.encode(KEY_SELECTED_SERVER, key)
|
||||
}
|
||||
}
|
||||
val profile = ProfileItem(
|
||||
@@ -79,7 +101,7 @@ object MmkvManager {
|
||||
server = config.getProxyOutbound()?.getServerAddress(),
|
||||
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||
)
|
||||
profileStorage?.encode(key, Gson().toJson(profile))
|
||||
profileStorage.encode(key, JsonUtil.toJson(profile))
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -87,22 +109,22 @@ object MmkvManager {
|
||||
if (guid.isBlank()) {
|
||||
return
|
||||
}
|
||||
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
|
||||
mainStorage?.remove(KEY_SELECTED_SERVER)
|
||||
if (getSelectServer() == guid) {
|
||||
mainStorage.remove(KEY_SELECTED_SERVER)
|
||||
}
|
||||
val serverList = decodeServerList()
|
||||
serverList.remove(guid)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
serverStorage?.remove(guid)
|
||||
profileStorage?.remove(guid)
|
||||
serverAffStorage?.remove(guid)
|
||||
encodeServerList(serverList)
|
||||
serverStorage.remove(guid)
|
||||
profileStorage.remove(guid)
|
||||
serverAffStorage.remove(guid)
|
||||
}
|
||||
|
||||
fun removeServerViaSubid(subid: String) {
|
||||
if (subid.isBlank()) {
|
||||
return
|
||||
}
|
||||
serverStorage?.allKeys()?.forEach { key ->
|
||||
serverStorage.allKeys()?.forEach { key ->
|
||||
decodeServerConfig(key)?.let { config ->
|
||||
if (config.subscriptionId == subid) {
|
||||
removeServer(key)
|
||||
@@ -115,11 +137,11 @@ object MmkvManager {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = serverAffStorage?.decodeString(guid)
|
||||
val json = serverAffStorage.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
|
||||
return JsonUtil.fromJson(json, ServerAffiliationInfo::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
|
||||
@@ -128,69 +150,23 @@ object MmkvManager {
|
||||
}
|
||||
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
|
||||
aff.testDelayMillis = testResult
|
||||
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
||||
serverAffStorage.encode(guid, JsonUtil.toJson(aff))
|
||||
}
|
||||
|
||||
fun clearAllTestDelayResults(keys: List<String>?) {
|
||||
keys?.forEach { key ->
|
||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||
aff.testDelayMillis = 0
|
||||
serverAffStorage?.encode(key, Gson().toJson(aff))
|
||||
serverAffStorage.encode(key, JsonUtil.toJson(aff))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importUrlAsSubscription(url: String): Int {
|
||||
val subscriptions = decodeSubscriptions()
|
||||
subscriptions.forEach {
|
||||
if (it.second.url == url) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
val uri = URI(Utils.fixIllegalUrl(url))
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = uri.fragment ?: "import sub"
|
||||
subItem.url = url
|
||||
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||
return 1
|
||||
}
|
||||
|
||||
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
|
||||
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
|
||||
subStorage?.allKeys()?.forEach { key ->
|
||||
val json = subStorage?.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||
}
|
||||
}
|
||||
return subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||
}
|
||||
|
||||
fun removeSubscription(subid: String) {
|
||||
subStorage?.remove(subid)
|
||||
removeServerViaSubid(subid)
|
||||
}
|
||||
|
||||
fun decodeAssetUrls(): List<Pair<String, AssetUrlItem>> {
|
||||
val assetUrlItems = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||
assetStorage?.allKeys()?.forEach { key ->
|
||||
val json = assetStorage?.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
||||
}
|
||||
}
|
||||
return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
}
|
||||
|
||||
fun removeAssetUrl(assetid: String) {
|
||||
assetStorage?.remove(assetid)
|
||||
}
|
||||
|
||||
fun removeAllServer() {
|
||||
mainStorage?.clearAll()
|
||||
serverStorage?.clearAll()
|
||||
profileStorage?.clearAll()
|
||||
serverAffStorage?.clearAll()
|
||||
mainStorage.clearAll()
|
||||
serverStorage.clearAll()
|
||||
profileStorage.clearAll()
|
||||
serverAffStorage.clearAll()
|
||||
}
|
||||
|
||||
fun removeInvalidServer(guid: String) {
|
||||
@@ -201,7 +177,7 @@ object MmkvManager {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serverAffStorage?.allKeys()?.forEach { key ->
|
||||
serverAffStorage.allKeys()?.forEach { key ->
|
||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||
if (aff.testDelayMillis < 0L) {
|
||||
removeServer(key)
|
||||
@@ -211,22 +187,138 @@ object MmkvManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||
|
||||
val serverDelays = mutableListOf<ServerDelay>()
|
||||
val serverList = decodeServerList()
|
||||
serverList.forEach { key ->
|
||||
val delay = decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
|
||||
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
|
||||
}
|
||||
serverDelays.sortBy { it.testDelayMillis }
|
||||
|
||||
serverDelays.forEach {
|
||||
serverList.remove(it.guid)
|
||||
serverList.add(it.guid)
|
||||
}
|
||||
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
fun encodeServerRaw(guid: String, config: String) {
|
||||
serverRawStorage.encode(guid, config)
|
||||
}
|
||||
|
||||
fun decodeServerRaw(guid: String): String? {
|
||||
return serverRawStorage.decodeString(guid) ?: return null
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Subscriptions
|
||||
|
||||
private fun initSubsList() {
|
||||
val subsList = decodeSubsList()
|
||||
if (subsList.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
subStorage.allKeys()?.forEach { key ->
|
||||
subsList.add(key)
|
||||
}
|
||||
encodeSubsList(subsList)
|
||||
}
|
||||
|
||||
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
|
||||
initSubsList()
|
||||
|
||||
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
|
||||
decodeSubsList().forEach { key ->
|
||||
val json = subStorage.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
subscriptions.add(Pair(key, JsonUtil.fromJson(json, SubscriptionItem::class.java)))
|
||||
}
|
||||
}
|
||||
return subscriptions
|
||||
}
|
||||
|
||||
fun removeSubscription(subid: String) {
|
||||
subStorage.remove(subid)
|
||||
val subsList = decodeSubsList()
|
||||
subsList.remove(subid)
|
||||
encodeSubsList(subsList)
|
||||
|
||||
removeServerViaSubid(subid)
|
||||
}
|
||||
|
||||
fun encodeSubscription(guid: String, subItem: SubscriptionItem) {
|
||||
val key = guid.ifBlank { Utils.getUuid() }
|
||||
subStorage.encode(key, JsonUtil.toJson(subItem))
|
||||
|
||||
val subsList = decodeSubsList()
|
||||
if (!subsList.contains(key)) {
|
||||
subsList.add(key)
|
||||
encodeSubsList(subsList)
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeSubscription(subscriptionId: String): SubscriptionItem? {
|
||||
val json = subStorage.decodeString(subscriptionId) ?: return null
|
||||
return JsonUtil.fromJson(json, SubscriptionItem::class.java)
|
||||
}
|
||||
|
||||
fun encodeSubsList(subsList: MutableList<String>) {
|
||||
mainStorage.encode(KEY_SUB_IDS, JsonUtil.toJson(subsList))
|
||||
}
|
||||
|
||||
fun decodeSubsList(): MutableList<String> {
|
||||
val json = mainStorage.decodeString(KEY_SUB_IDS)
|
||||
return if (json.isNullOrBlank()) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Asset
|
||||
|
||||
fun decodeAssetUrls(): List<Pair<String, AssetUrlItem>> {
|
||||
val assetUrlItems = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||
assetStorage.allKeys()?.forEach { key ->
|
||||
val json = assetStorage.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetUrlItems.add(Pair(key, JsonUtil.fromJson(json, AssetUrlItem::class.java)))
|
||||
}
|
||||
}
|
||||
return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
}
|
||||
|
||||
fun removeAssetUrl(assetid: String) {
|
||||
assetStorage.remove(assetid)
|
||||
}
|
||||
|
||||
fun encodeAsset(assetid: String, assetItem: AssetUrlItem) {
|
||||
val key = assetid.ifBlank { Utils.getUuid() }
|
||||
assetStorage.encode(key, JsonUtil.toJson(assetItem))
|
||||
}
|
||||
|
||||
fun decodeAsset(assetid: String): AssetUrlItem? {
|
||||
val json = assetStorage.decodeString(assetid) ?: return null
|
||||
return JsonUtil.fromJson(json, AssetUrlItem::class.java)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Routing
|
||||
|
||||
fun decodeRoutingRulesets(): MutableList<RulesetItem>? {
|
||||
val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET)
|
||||
if (ruleset.isNullOrEmpty()) return null
|
||||
return JsonUtil.fromJson(ruleset, Array<RulesetItem>::class.java).toMutableList()
|
||||
}
|
||||
|
||||
fun encodeRoutingRulesets(rulesetList: MutableList<RulesetItem>?) {
|
||||
if (rulesetList.isNullOrEmpty())
|
||||
settingsStorage.encode(PREF_ROUTING_RULESET, "")
|
||||
else
|
||||
settingsStorage.encode(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList))
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Others
|
||||
|
||||
fun encodeStartOnBoot(startOnBoot: Boolean) {
|
||||
settingsStorage.encode(PREF_IS_BOOTED, startOnBoot)
|
||||
}
|
||||
|
||||
fun decodeStartOnBoot(): Boolean {
|
||||
return settingsStorage.decodeBool(PREF_IS_BOOTED, false)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
}
|
||||
|
||||
99
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/PluginUtil.kt
Normal file
99
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/PluginUtil.kt
Normal file
@@ -0,0 +1,99 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.service.ProcessService
|
||||
import com.v2ray.ang.util.fmt.Hysteria2Fmt
|
||||
import java.io.File
|
||||
|
||||
object PluginUtil {
|
||||
//private const val HYSTERIA2 = "hysteria2-plugin"
|
||||
private const val HYSTERIA2 = "libhysteria2.so"
|
||||
private const val TAG = ANG_PACKAGE
|
||||
private lateinit var procService: ProcessService
|
||||
|
||||
// fun initPlugin(name: String): PluginManager.InitResult {
|
||||
// return PluginManager.init(name)!!
|
||||
// }
|
||||
|
||||
fun runPlugin(context: Context, config: ServerConfig?, domainPort: String?) {
|
||||
Log.d(TAG, "runPlugin")
|
||||
|
||||
val outbound = config?.getProxyOutbound() ?: return
|
||||
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
|
||||
val configFile = genConfigHy2(context, config, domainPort) ?: return
|
||||
val cmd = genCmdHy2(context, configFile)
|
||||
|
||||
procService = ProcessService()
|
||||
procService.runProcess(context, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopPlugin() {
|
||||
stopHy2()
|
||||
}
|
||||
|
||||
fun realPingHy2(context: Context, config: ServerConfig?): Long {
|
||||
Log.d(TAG, "realPingHy2")
|
||||
val retFailure = -1L
|
||||
|
||||
val outbound = config?.getProxyOutbound() ?: return retFailure
|
||||
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
|
||||
val socksPort = Utils.findFreePort(listOf(0))
|
||||
val configFile = genConfigHy2(context, config, "0:${socksPort}") ?: return retFailure
|
||||
val cmd = genCmdHy2(context, configFile)
|
||||
|
||||
val proc = ProcessService()
|
||||
proc.runProcess(context, cmd)
|
||||
Thread.sleep(1000L)
|
||||
val delay = SpeedtestUtil.testConnection(context, socksPort)
|
||||
proc.stopProcess()
|
||||
|
||||
return delay.first
|
||||
}
|
||||
return retFailure
|
||||
}
|
||||
|
||||
private fun genConfigHy2(context: Context, config: ServerConfig, domainPort: String?): File? {
|
||||
Log.d(TAG, "runPlugin $HYSTERIA2")
|
||||
|
||||
val socksPort = domainPort?.split(":")?.last()
|
||||
.let { if (it.isNullOrEmpty()) return null else it.toInt() }
|
||||
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return null
|
||||
|
||||
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
|
||||
Log.d(TAG, "runPlugin ${configFile.absolutePath}")
|
||||
|
||||
configFile.parentFile?.mkdirs()
|
||||
configFile.writeText(JsonUtil.toJson(hy2Config))
|
||||
Log.d(TAG, JsonUtil.toJson(hy2Config))
|
||||
|
||||
return configFile
|
||||
}
|
||||
|
||||
private fun genCmdHy2(context: Context, configFile: File): MutableList<String> {
|
||||
return mutableListOf(
|
||||
File(context.applicationInfo.nativeLibraryDir, HYSTERIA2).absolutePath,
|
||||
//initPlugin(HYSTERIA2).path,
|
||||
"--disable-update-check",
|
||||
"--config",
|
||||
configFile.absolutePath,
|
||||
"--log-level",
|
||||
"warn",
|
||||
"client"
|
||||
)
|
||||
}
|
||||
|
||||
private fun stopHy2() {
|
||||
try {
|
||||
Log.d(TAG, "$HYSTERIA2 destroy")
|
||||
procService?.stopProcess()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
|
||||
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeServerConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeServerList
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils.parseInt
|
||||
import java.util.Collections
|
||||
|
||||
object SettingsManager {
|
||||
|
||||
fun initRoutingRulesets(context: Context) {
|
||||
val exist = MmkvManager.decodeRoutingRulesets()
|
||||
if (exist.isNullOrEmpty()) {
|
||||
val rulesetList = getPresetRoutingRulesets(context)
|
||||
MmkvManager.encodeRoutingRulesets(rulesetList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPresetRoutingRulesets(context: Context, index: Int = 0): MutableList<RulesetItem>? {
|
||||
val fileName = when (index) {
|
||||
0 -> "custom_routing_white"
|
||||
1 -> "custom_routing_black"
|
||||
2 -> "custom_routing_global"
|
||||
else -> "custom_routing_white"
|
||||
}
|
||||
val assets = Utils.readTextFromAssets(context, fileName)
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return JsonUtil.fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
|
||||
}
|
||||
|
||||
fun resetRoutingRulesets(context: Context, index: Int) {
|
||||
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
|
||||
resetRoutingRulesetsCommon(rulesetList)
|
||||
}
|
||||
|
||||
fun resetRoutingRulesetsFromClipboard(content: String?): Boolean {
|
||||
if (content.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
val rulesetList = JsonUtil.fromJson(content, Array<RulesetItem>::class.java).toMutableList()
|
||||
if (rulesetList.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
resetRoutingRulesetsCommon(rulesetList)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetRoutingRulesetsCommon(rulesetList: MutableList<RulesetItem>) {
|
||||
val rulesetNew: MutableList<RulesetItem> = mutableListOf()
|
||||
MmkvManager.decodeRoutingRulesets()?.forEach { key ->
|
||||
if (key.looked == true) {
|
||||
rulesetNew.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
rulesetNew.addAll(rulesetList)
|
||||
MmkvManager.encodeRoutingRulesets(rulesetNew)
|
||||
}
|
||||
|
||||
fun getRoutingRuleset(index: Int): RulesetItem? {
|
||||
if (index < 0) return null
|
||||
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) return null
|
||||
|
||||
return rulesetList[index]
|
||||
}
|
||||
|
||||
fun saveRoutingRuleset(index: Int, ruleset: RulesetItem?) {
|
||||
if (ruleset == null) return
|
||||
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) return
|
||||
|
||||
if (index < 0 || index >= rulesetList.count()) {
|
||||
rulesetList.add(ruleset)
|
||||
} else {
|
||||
rulesetList[index] = ruleset
|
||||
}
|
||||
MmkvManager.encodeRoutingRulesets(rulesetList)
|
||||
}
|
||||
|
||||
fun removeRoutingRuleset(index: Int) {
|
||||
if (index < 0) return
|
||||
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) return
|
||||
|
||||
rulesetList.removeAt(index)
|
||||
MmkvManager.encodeRoutingRulesets(rulesetList)
|
||||
}
|
||||
|
||||
fun routingRulesetsBypassLan(): Boolean {
|
||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||
val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
|
||||
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
|
||||
}
|
||||
return exist == true
|
||||
}
|
||||
|
||||
fun swapRoutingRuleset(fromPosition: Int, toPosition: Int) {
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) return
|
||||
|
||||
Collections.swap(rulesetList, fromPosition, toPosition)
|
||||
MmkvManager.encodeRoutingRulesets(rulesetList)
|
||||
}
|
||||
|
||||
fun swapSubscriptions(fromPosition: Int, toPosition: Int) {
|
||||
val subsList = MmkvManager.decodeSubsList()
|
||||
if (subsList.isNullOrEmpty()) return
|
||||
|
||||
Collections.swap(subsList, fromPosition, toPosition)
|
||||
MmkvManager.encodeSubsList(subsList)
|
||||
}
|
||||
|
||||
fun getServerViaRemarks(remarks: String?): ServerConfig? {
|
||||
if (remarks == null) {
|
||||
return null
|
||||
}
|
||||
val serverList = decodeServerList()
|
||||
for (guid in serverList) {
|
||||
val profile = decodeProfileConfig(guid)
|
||||
if (profile != null && profile.remarks == remarks) {
|
||||
return decodeServerConfig(guid)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getSocksPort(): Int {
|
||||
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
}
|
||||
|
||||
fun getHttpPort(): Int {
|
||||
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.responseLength
|
||||
import kotlinx.coroutines.isActive
|
||||
@@ -97,9 +98,9 @@ object SpeedtestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun testConnection(context: Context, port: Int): String {
|
||||
// return V2RayVpnService.measureV2rayDelay()
|
||||
fun testConnection(context: Context, port: Int): Pair<Long, String> {
|
||||
var result: String
|
||||
var elapsed = -1L
|
||||
var conn: HttpURLConnection? = null
|
||||
|
||||
try {
|
||||
@@ -108,7 +109,7 @@ object SpeedtestUtil {
|
||||
conn = url.openConnection(
|
||||
Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", port)
|
||||
InetSocketAddress(LOOPBACK, port)
|
||||
)
|
||||
) as HttpURLConnection
|
||||
conn.connectTimeout = 30000
|
||||
@@ -119,7 +120,7 @@ object SpeedtestUtil {
|
||||
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
val code = conn.responseCode
|
||||
val elapsed = SystemClock.elapsedRealtime() - start
|
||||
elapsed = SystemClock.elapsedRealtime() - start
|
||||
|
||||
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
||||
result = context.getString(R.string.connection_test_available, elapsed)
|
||||
@@ -133,10 +134,7 @@ object SpeedtestUtil {
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// 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)
|
||||
} catch (e: Exception) {
|
||||
// library exception, eg sumsung
|
||||
@@ -146,7 +144,7 @@ object SpeedtestUtil {
|
||||
conn?.disconnect()
|
||||
}
|
||||
|
||||
return result
|
||||
return Pair(elapsed, result)
|
||||
}
|
||||
|
||||
fun getLibVersion(): String {
|
||||
|
||||
@@ -17,22 +17,20 @@ import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.webkit.URLUtil
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
import java.util.*
|
||||
|
||||
object Utils {
|
||||
|
||||
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) }
|
||||
|
||||
/**
|
||||
* convert string to editalbe for kotlin
|
||||
*
|
||||
@@ -246,7 +244,7 @@ object Utils {
|
||||
}
|
||||
|
||||
fun startVServiceFromToggle(context: Context): Boolean {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
|
||||
context.toast(R.string.app_tile_first_use)
|
||||
return false
|
||||
}
|
||||
@@ -362,7 +360,7 @@ object Utils {
|
||||
url.openConnection(
|
||||
Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", httpPort)
|
||||
InetSocketAddress(LOOPBACK, httpPort)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -455,6 +453,17 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
fun findFreePort(ports: List<Int>): Int {
|
||||
for (port in ports) {
|
||||
try {
|
||||
return ServerSocket(port).use { it.localPort }
|
||||
} catch (ex: IOException) {
|
||||
continue // try next port
|
||||
}
|
||||
}
|
||||
|
||||
// if the program gets here, no port in the range was found
|
||||
throw IOException("no free port found")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
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.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.GEOIP_CN
|
||||
import com.v2ray.ang.AppConfig.GEOSITE_CN
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
||||
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
@@ -14,89 +17,69 @@ 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_V6
|
||||
import com.v2ray.ang.dto.ConfigResult
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ERoutingMode
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
|
||||
object V2rayConfigUtil {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
data class Result(var status: Boolean, var content: String)
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
fun getV2rayConfig(context: Context, guid: String): Result {
|
||||
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
|
||||
try {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val raw = serverRawStorage?.decodeString(guid)
|
||||
val raw = MmkvManager.decodeServerRaw(guid)
|
||||
val customConfig = if (raw.isNullOrBlank()) {
|
||||
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
|
||||
config.fullConfig?.toPrettyPrinting() ?: return ConfigResult(false)
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
//Log.d(ANG_PACKAGE, customConfig)
|
||||
return Result(true, customConfig)
|
||||
}
|
||||
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 domainPort = config.getProxyOutbound()?.getServerAddressAndPort()
|
||||
return ConfigResult(true, guid, customConfig, domainPort)
|
||||
}
|
||||
|
||||
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
||||
val result = getV2rayNonCustomConfig(context, config)
|
||||
//Log.d(ANG_PACKAGE, result.content)
|
||||
result.guid = guid
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Result(false, "")
|
||||
return ConfigResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
private fun getV2rayNonCustomConfig(
|
||||
context: Context,
|
||||
outbound: V2rayConfig.OutboundBean,
|
||||
remarks: String,
|
||||
): Result {
|
||||
val result = Result(false, "")
|
||||
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): ConfigResult {
|
||||
val result = ConfigResult(false)
|
||||
|
||||
val outbound = config.getProxyOutbound() ?: return result
|
||||
val address = outbound.getServerAddress() ?: return result
|
||||
if (!Utils.isIpAddress(address)) {
|
||||
if (!Utils.isValidUrl(address)) {
|
||||
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
//取得默认配置
|
||||
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
|
||||
//转成Json
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
|
||||
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL)
|
||||
?: "warning"
|
||||
val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
v2rayConfig.remarks = config.remarks
|
||||
|
||||
inbounds(v2rayConfig)
|
||||
|
||||
updateOutboundWithGlobalSettings(outbound)
|
||||
v2rayConfig.outbounds[0] = outbound
|
||||
val isPlugin = outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||
val retOut = outbounds(v2rayConfig, outbound, isPlugin)
|
||||
|
||||
updateOutboundFragment(v2rayConfig)
|
||||
val retMore = moreOutbounds(v2rayConfig, config.subscriptionId, isPlugin)
|
||||
|
||||
routing(v2rayConfig)
|
||||
|
||||
@@ -112,31 +95,21 @@ object V2rayConfigUtil {
|
||||
v2rayConfig.policy = null
|
||||
}
|
||||
|
||||
v2rayConfig.remarks = remarks
|
||||
|
||||
result.status = true
|
||||
result.content = v2rayConfig.toPrettyPrinting()
|
||||
result.domainPort = if (retMore.first) retMore.second else retOut.second
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val socksPort = Utils.parseInt(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT),
|
||||
AppConfig.PORT_SOCKS.toInt()
|
||||
)
|
||||
val httpPort = Utils.parseInt(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||
AppConfig.PORT_HTTP.toInt()
|
||||
)
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
|
||||
v2rayConfig.inbounds.forEach { curInbound ->
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
||||
//bind all inbounds to localhost if the user requests
|
||||
curInbound.listen = "127.0.0.1"
|
||||
curInbound.listen = LOOPBACK
|
||||
}
|
||||
}
|
||||
v2rayConfig.inbounds[0].port = socksPort
|
||||
@@ -170,6 +143,42 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean, isPlugin: Boolean): Pair<Boolean, String> {
|
||||
if (isPlugin) {
|
||||
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
|
||||
val outboundNew = V2rayConfig.OutboundBean(
|
||||
mux = null,
|
||||
protocol = EConfigType.SOCKS.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
servers = listOf(
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean(
|
||||
address = LOOPBACK,
|
||||
port = socksPort
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
if (v2rayConfig.outbounds.isNotEmpty()) {
|
||||
v2rayConfig.outbounds[0] = outboundNew
|
||||
} else {
|
||||
v2rayConfig.outbounds.add(outboundNew)
|
||||
}
|
||||
return Pair(true, outboundNew.getServerAddressAndPort())
|
||||
}
|
||||
|
||||
val ret = updateOutboundWithGlobalSettings(outbound)
|
||||
if (!ret) return Pair(false, "")
|
||||
|
||||
if (v2rayConfig.outbounds.isNotEmpty()) {
|
||||
v2rayConfig.outbounds[0] = outbound
|
||||
} else {
|
||||
v2rayConfig.outbounds.add(outbound)
|
||||
}
|
||||
|
||||
updateOutboundFragment(v2rayConfig)
|
||||
return Pair(true, outbound.getServerAddressAndPort())
|
||||
}
|
||||
|
||||
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|
||||
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
||||
@@ -178,89 +187,15 @@ object V2rayConfigUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* routing
|
||||
*/
|
||||
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||
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(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||
)
|
||||
} else {
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||
)
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
.orEmpty(), TAG_DIRECT, v2rayConfig
|
||||
)
|
||||
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "IPIfNonMatch"
|
||||
|
||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||
rulesetItems?.forEach { key ->
|
||||
routingUserRule(key, v2rayConfig)
|
||||
}
|
||||
|
||||
v2rayConfig.routing.domainStrategy =
|
||||
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||
?: "IPIfNonMatch"
|
||||
|
||||
// Hardcode googleapis.cn gstatic.com
|
||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = TAG_PROXY,
|
||||
domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com")
|
||||
)
|
||||
|
||||
when (routingMode) {
|
||||
ERoutingMode.BYPASS_LAN.value -> {
|
||||
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||
}
|
||||
|
||||
ERoutingMode.BYPASS_MAINLAND.value -> {
|
||||
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||
}
|
||||
|
||||
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
|
||||
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||
}
|
||||
|
||||
ERoutingMode.GLOBAL_DIRECT.value -> {
|
||||
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = TAG_DIRECT,
|
||||
)
|
||||
if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") {
|
||||
globalDirect.port = "0-65535"
|
||||
} else {
|
||||
globalDirect.ip = arrayListOf("0.0.0.0/0", "::/0")
|
||||
}
|
||||
v2rayConfig.routing.rules.add(globalDirect)
|
||||
}
|
||||
}
|
||||
|
||||
if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) {
|
||||
val globalProxy = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = TAG_PROXY,
|
||||
)
|
||||
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) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
@@ -268,96 +203,46 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun routingGeo(
|
||||
ipOrDomain: String,
|
||||
code: String,
|
||||
tag: String,
|
||||
v2rayConfig: V2rayConfig
|
||||
) {
|
||||
private fun routingUserRule(item: RulesetItem?, v2rayConfig: V2rayConfig) {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(code)) {
|
||||
//IP
|
||||
if (ipOrDomain == "ip" || ipOrDomain == "") {
|
||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesIP.outboundTag = tag
|
||||
rulesIP.ip = ArrayList()
|
||||
rulesIP.ip?.add("geoip:$code")
|
||||
v2rayConfig.routing.rules.add(rulesIP)
|
||||
}
|
||||
|
||||
if (ipOrDomain == "domain" || ipOrDomain == "") {
|
||||
//Domain
|
||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesDomain.outboundTag = tag
|
||||
rulesDomain.domain = ArrayList()
|
||||
rulesDomain.domain?.add("geosite:$code")
|
||||
v2rayConfig.routing.rules.add(rulesDomain)
|
||||
}
|
||||
if (item == null || !item.enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val rule = JsonUtil.fromJson(JsonUtil.toJson(item), RulesBean::class.java) ?: return
|
||||
|
||||
v2rayConfig.routing.rules.add(rule)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun routingUserRule(userRule: String, tag: String, v2rayConfig: V2rayConfig) {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(userRule)) {
|
||||
//Domain
|
||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesDomain.outboundTag = tag
|
||||
rulesDomain.domain = ArrayList()
|
||||
private fun userRule2Domain(tag: String): ArrayList<String> {
|
||||
val domain = ArrayList<String>()
|
||||
|
||||
//IP
|
||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesIP.outboundTag = tag
|
||||
rulesIP.ip = ArrayList()
|
||||
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (it.startsWith("ext:") && it.contains("geoip")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (it.isNotEmpty()) {
|
||||
rulesDomain.domain?.add(it)
|
||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||
rulesetItems?.forEach { key ->
|
||||
if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) {
|
||||
key.domain?.forEach {
|
||||
if (it != GEOSITE_PRIVATE
|
||||
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
|
||||
) {
|
||||
domain.add(it)
|
||||
}
|
||||
}
|
||||
if ((rulesDomain.domain?.size ?: 0) > 0) {
|
||||
v2rayConfig.routing.rules.add(rulesDomain)
|
||||
}
|
||||
if ((rulesIP.ip?.size ?: 0) > 0) {
|
||||
v2rayConfig.routing.rules.add(rulesIP)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun userRule2Domain(userRule: String): ArrayList<String> {
|
||||
val domain = ArrayList<String>()
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||
domain.add(it)
|
||||
}
|
||||
}
|
||||
return domain
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Dns
|
||||
*/
|
||||
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
val proxyDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
.orEmpty()
|
||||
)
|
||||
val directDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
.orEmpty()
|
||||
)
|
||||
val geositeCn = arrayListOf(GEOSITE_CN)
|
||||
val proxyDomain = userRule2Domain(TAG_PROXY)
|
||||
val directDomain = userRule2Domain(TAG_DIRECT)
|
||||
// fakedns with all domains to make it always top priority
|
||||
v2rayConfig.dns.servers?.add(
|
||||
0,
|
||||
@@ -385,7 +270,7 @@ object V2rayConfigUtil {
|
||||
V2rayConfig.InboundBean(
|
||||
tag = "dns-in",
|
||||
port = localDnsPort,
|
||||
listen = "127.0.0.1",
|
||||
listen = LOOPBACK,
|
||||
protocol = "dokodemo-door",
|
||||
settings = dnsInboundSettings,
|
||||
sniffing = null
|
||||
@@ -428,10 +313,7 @@ object V2rayConfigUtil {
|
||||
|
||||
//remote Dns
|
||||
val remoteDns = Utils.getRemoteDnsServers()
|
||||
val proxyDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
.orEmpty()
|
||||
)
|
||||
val proxyDomain = userRule2Domain(TAG_PROXY)
|
||||
remoteDns.forEach {
|
||||
servers.add(it)
|
||||
}
|
||||
@@ -448,16 +330,9 @@ object V2rayConfigUtil {
|
||||
|
||||
// domestic DNS
|
||||
val domesticDns = Utils.getDomesticDnsServers()
|
||||
val directDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
.orEmpty()
|
||||
)
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||
val isCnRoutingMode =
|
||||
(routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value)
|
||||
val geoipCn = arrayListOf("geoip:cn")
|
||||
|
||||
val directDomain = userRule2Domain(TAG_DIRECT)
|
||||
val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
|
||||
val geoipCn = arrayListOf(GEOIP_CN)
|
||||
if (directDomain.size > 0) {
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
@@ -468,17 +343,6 @@ object V2rayConfigUtil {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isCnRoutingMode) {
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
domesticDns.first(),
|
||||
53,
|
||||
geositeCn,
|
||||
geoipCn
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
@@ -492,12 +356,9 @@ object V2rayConfigUtil {
|
||||
}
|
||||
|
||||
//block dns
|
||||
val blkDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
.orEmpty()
|
||||
)
|
||||
val blkDomain = userRule2Domain(TAG_BLOCKED)
|
||||
if (blkDomain.size > 0) {
|
||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||
hosts.putAll(blkDomain.map { it to LOOPBACK })
|
||||
}
|
||||
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
@@ -539,8 +400,10 @@ object V2rayConfigUtil {
|
||||
val protocol = outbound.protocol
|
||||
if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.HTTP.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.WIREGUARD.name, true)
|
||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||
) {
|
||||
muxEnabled = false
|
||||
} else if (protocol.equals(EConfigType.VLESS.name, true)
|
||||
@@ -582,7 +445,7 @@ object V2rayConfigUtil {
|
||||
val requestString: String by lazy {
|
||||
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
|
||||
}
|
||||
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(
|
||||
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
|
||||
requestString,
|
||||
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
|
||||
)
|
||||
@@ -641,9 +504,12 @@ object V2rayConfigUtil {
|
||||
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
|
||||
?: "10-20"
|
||||
),
|
||||
noise = V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
|
||||
packet = "rand:100-200",
|
||||
delay = "10-20",
|
||||
noises = listOf(
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
|
||||
type = "rand",
|
||||
packet = "100-200",
|
||||
delay = "10-20",
|
||||
)
|
||||
),
|
||||
)
|
||||
fragmentOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean(
|
||||
@@ -665,4 +531,67 @@ object V2rayConfigUtil {
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun moreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String, isPlugin: Boolean): Pair<Boolean, String> {
|
||||
val returnPair = Pair(false, "")
|
||||
var domainPort: String = ""
|
||||
|
||||
if (isPlugin) {
|
||||
return returnPair
|
||||
}
|
||||
//fragment proxy
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
|
||||
return returnPair
|
||||
}
|
||||
|
||||
if (subscriptionId.isNullOrEmpty()) {
|
||||
return returnPair
|
||||
}
|
||||
try {
|
||||
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return returnPair
|
||||
|
||||
//current proxy
|
||||
val outbound = v2rayConfig.outbounds[0]
|
||||
|
||||
//Previous proxy
|
||||
val prevNode = SettingsManager.getServerViaRemarks(subItem.prevProfile)
|
||||
if (prevNode != null) {
|
||||
val prevOutbound = prevNode.getProxyOutbound()
|
||||
if (prevOutbound != null) {
|
||||
updateOutboundWithGlobalSettings(prevOutbound)
|
||||
prevOutbound.tag = TAG_PROXY + "2"
|
||||
v2rayConfig.outbounds.add(prevOutbound)
|
||||
outbound.streamSettings?.sockopt =
|
||||
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
|
||||
dialerProxy = prevOutbound.tag
|
||||
)
|
||||
domainPort = prevOutbound.getServerAddressAndPort()
|
||||
}
|
||||
}
|
||||
|
||||
//Next proxy
|
||||
val nextNode = SettingsManager.getServerViaRemarks(subItem.nextProfile)
|
||||
if (nextNode != null) {
|
||||
val nextOutbound = nextNode.getProxyOutbound()
|
||||
if (nextOutbound != null) {
|
||||
updateOutboundWithGlobalSettings(nextOutbound)
|
||||
nextOutbound.tag = TAG_PROXY
|
||||
v2rayConfig.outbounds.add(0, nextOutbound)
|
||||
outbound.tag = TAG_PROXY + "1"
|
||||
nextOutbound.streamSettings?.sockopt =
|
||||
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
|
||||
dialerProxy = outbound.tag
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return returnPair
|
||||
}
|
||||
|
||||
if (domainPort.isNotEmpty()) {
|
||||
return Pair(true, domainPort)
|
||||
}
|
||||
return returnPair
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.v2ray.ang.util.fmt
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.Hysteria2Bean
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
object Hysteria2Fmt {
|
||||
|
||||
fun parse(str: String): ServerConfig {
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.HYSTERIA2)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
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?.streamSettings?.populateTlsSettings(
|
||||
V2rayConfig.TLS,
|
||||
if ((queryParam["insecure"].orEmpty()) == "1") true else allowInsecure,
|
||||
queryParam["sni"] ?: uri.idnHost,
|
||||
null,
|
||||
queryParam["alpn"],
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
server.address = uri.idnHost
|
||||
server.port = uri.port
|
||||
server.password = uri.userInfo
|
||||
}
|
||||
if (!queryParam["obfs-password"].isNullOrEmpty()) {
|
||||
config.outboundBean?.settings?.obfsPassword = queryParam["obfs-password"]
|
||||
}
|
||||
|
||||
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>()
|
||||
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||
streamSetting.tlsSettings?.let { tlsSetting ->
|
||||
dicQuery["insecure"] = if (tlsSetting.allowInsecure) "1" else "0"
|
||||
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 (!outbound.settings?.obfsPassword.isNullOrEmpty()) {
|
||||
dicQuery["obfs"] = "salamander"
|
||||
dicQuery["obfs-password"] = outbound.settings?.obfsPassword ?: ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fun toNativeConfig(config: ServerConfig, socksPort: Int): Hysteria2Bean? {
|
||||
val outbound = config.getProxyOutbound() ?: return null
|
||||
val tls = outbound.streamSettings?.tlsSettings
|
||||
val obfs = if (outbound.settings?.obfsPassword.isNullOrEmpty()) null else
|
||||
Hysteria2Bean.ObfsBean(
|
||||
type = "salamander",
|
||||
salamander = Hysteria2Bean.ObfsBean.SalamanderBean(
|
||||
password = outbound.settings?.obfsPassword
|
||||
)
|
||||
)
|
||||
|
||||
val bean = Hysteria2Bean(
|
||||
server = outbound.getServerAddressAndPort(),
|
||||
auth = outbound.getPassword(),
|
||||
obfs = obfs,
|
||||
socks5 = Hysteria2Bean.Socks5Bean(
|
||||
listen = "$LOOPBACK:${socksPort}",
|
||||
),
|
||||
http = Hysteria2Bean.Socks5Bean(
|
||||
listen = "$LOOPBACK:${socksPort}",
|
||||
),
|
||||
tls = Hysteria2Bean.TlsBean(
|
||||
sni = tls?.serverName ?: outbound.getServerAddress(),
|
||||
insecure = tls?.allowInsecure
|
||||
)
|
||||
)
|
||||
return bean
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
object ShadowsocksFmt {
|
||||
fun parseShadowsocks(str: String): ServerConfig? {
|
||||
fun parse(str: String): ServerConfig? {
|
||||
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
||||
if (!tryResolveResolveSip002(str, config)) {
|
||||
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
object SocksFmt {
|
||||
fun parseSocks(str: String): ServerConfig? {
|
||||
fun parse(str: String): ServerConfig? {
|
||||
val config = ServerConfig.create(EConfigType.SOCKS)
|
||||
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
||||
val indexSplit = result.indexOf("#")
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
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.MmkvManager.settingsStorage
|
||||
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 {
|
||||
fun parse(str: String): ServerConfig {
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.TROJAN)
|
||||
|
||||
@@ -99,7 +92,7 @@ object TrojanFmt {
|
||||
}
|
||||
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||
dicQuery["alpn"] =
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
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.MmkvManager.settingsStorage
|
||||
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? {
|
||||
fun parse(str: String): ServerConfig? {
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.VLESS)
|
||||
|
||||
@@ -90,7 +83,7 @@ object VlessFmt {
|
||||
}
|
||||
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||
dicQuery["alpn"] =
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||
|
||||
@@ -2,26 +2,21 @@ 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.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
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? {
|
||||
fun parse(str: String): ServerConfig? {
|
||||
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
|
||||
return parseVmessStd(str)
|
||||
}
|
||||
@@ -35,7 +30,7 @@ object VmessFmt {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
|
||||
return null
|
||||
}
|
||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||
val vmessQRCode = JsonUtil.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)
|
||||
@@ -99,14 +94,14 @@ object VmessFmt {
|
||||
vmessQRCode.tls = streamSetting.security
|
||||
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
||||
vmessQRCode.alpn =
|
||||
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
|
||||
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)
|
||||
val json = JsonUtil.toJson(vmessQRCode)
|
||||
return Utils.encode(json)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
object WireguardFmt {
|
||||
fun parseWireguard(str: String): ServerConfig? {
|
||||
fun parse(str: String): ServerConfig? {
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
if (uri.rawQuery != null) {
|
||||
val config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||
@@ -81,12 +81,12 @@ object WireguardFmt {
|
||||
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
|
||||
if (outbound.settings?.reserved != null) {
|
||||
dicQuery["reserved"] = Utils.urlEncode(
|
||||
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
|
||||
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString(","))
|
||||
.toString()
|
||||
)
|
||||
}
|
||||
dicQuery["address"] = Utils.urlEncode(
|
||||
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
|
||||
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString(","))
|
||||
.toString()
|
||||
)
|
||||
if (outbound.settings?.mtu != null) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
@@ -20,18 +19,16 @@ import com.v2ray.ang.dto.EConfigType
|
||||
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.serializable
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
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.subStorage
|
||||
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
|
||||
@@ -101,10 +98,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
try {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.subscriptionId = subscriptionId
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.serverRawStorage?.encode(key, server)
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
serverList.add(0, key)
|
||||
val profile = ProfileItem(
|
||||
configType = config.configType,
|
||||
@@ -125,7 +122,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||
Collections.swap(serverList, fromPosition, toPosition)
|
||||
Collections.swap(serversCache, fromPosition, toPosition)
|
||||
MmkvManager.mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
MmkvManager.encodeServerList(serverList)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -159,12 +156,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
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
|
||||
}
|
||||
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return 0
|
||||
return updateConfigViaSub(Pair(subscriptionId, subItem))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,14 +211,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
|
||||
for (item in serversCopy) {
|
||||
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid)
|
||||
if (config.status) {
|
||||
MessageUtil.sendMsg2TestService(
|
||||
getApplication(),
|
||||
AppConfig.MSG_MEASURE_CONFIG,
|
||||
Pair(item.guid, config.content)
|
||||
)
|
||||
}
|
||||
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +303,22 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
MmkvManager.sortByTestResults()
|
||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||
|
||||
val serverDelays = mutableListOf<ServerDelay>()
|
||||
val serverList = MmkvManager.decodeServerList()
|
||||
serverList.forEach { key ->
|
||||
val delay = MmkvManager.decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
|
||||
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
|
||||
}
|
||||
serverDelays.sortBy { it.testDelayMillis }
|
||||
|
||||
serverDelays.forEach {
|
||||
serverList.remove(it.guid)
|
||||
serverList.add(it.guid)
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerList(serverList)
|
||||
}
|
||||
|
||||
|
||||
@@ -386,11 +387,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||
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>
|
||||
}
|
||||
val resultPair = intent.serializable<Pair<String, Long>>("content") ?: return
|
||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||
updateListAction.value = getPosition(resultPair.first)
|
||||
}
|
||||
|
||||
@@ -5,21 +5,13 @@ import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class SettingsViewModel(application: Application) : AndroidViewModel(application),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val settingsStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_SETTING,
|
||||
MMKV.MULTI_PROCESS_MODE
|
||||
)
|
||||
}
|
||||
|
||||
fun startListenPreferenceChange() {
|
||||
PreferenceManager.getDefaultSharedPreferences(getApplication())
|
||||
.registerOnSharedPreferenceChangeListener(this)
|
||||
@@ -47,10 +39,6 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
AppConfig.PREF_LANGUAGE,
|
||||
AppConfig.PREF_UI_MODE_NIGHT,
|
||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||
AppConfig.PREF_ROUTING_MODE,
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT,
|
||||
AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,
|
||||
AppConfig.PREF_FRAGMENT_PACKETS,
|
||||
AppConfig.PREF_FRAGMENT_LENGTH,
|
||||
@@ -61,6 +49,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
}
|
||||
|
||||
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||
AppConfig.PREF_IS_BOOTED,
|
||||
AppConfig.PREF_SPEED_ENABLED,
|
||||
AppConfig.PREF_PROXY_SHARING,
|
||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||
|
||||
11
V2rayNG/app/src/main/res/drawable-night/ic_lock_24dp.xml
Normal file
11
V2rayNG/app/src/main/res/drawable-night/ic_lock_24dp.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M742.4,409.6L716.8,409.6L716.8,332.8C716.8,205.8 613.4,102.4 486.4,102.4S256,205.8 256,332.8L256,409.6h-25.6C188.1,409.6 153.6,444.1 153.6,486.4v409.6c0,42.3 34.5,76.8 76.8,76.8h512c42.3,0 76.8,-34.5 76.8,-76.8v-409.6c0,-42.3 -34.5,-76.8 -76.8,-76.8zM307.2,332.8C307.2,234 387.6,153.6 486.4,153.6S665.6,234 665.6,332.8L665.6,409.6L307.2,409.6L307.2,332.8zM768,896a25.6,25.6 0,0 1,-25.6 25.6h-512a25.6,25.6 0,0 1,-25.6 -25.6v-409.6a25.6,25.6 0,0 1,25.6 -25.6h512a25.6,25.6 0,0 1,25.6 25.6v409.6z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M740,161a112.1,112.1 0,0 0,-33.5 218.9v95.9L320,602.4L320,318.1a112.1,112.1 0,1 0,-148 -106.1c0,49.3 31.8,91 76,106.1v387.9a112.1,112.1 0,1 0,148 106.1c0,-49.2 -31.8,-91 -76,-106.1v-27.8l423.5,-138.7a50.6,50.6 0,0 0,34.9 -48.2L778.4,378.2c42.9,-15.8 73.6,-57 73.6,-105.2 0,-61.8 -50.2,-112 -112,-112zM236,212a48,48 0,1 1,96 0,48 48,0 0,1 -96,0zM332,812a48,48 0,1 1,-96 0,48 48,0 0,1 96,0zM740,321a48,48 0,1 1,0 -96,48 48,0 0,1 0,96z" />
|
||||
</vector>
|
||||
11
V2rayNG/app/src/main/res/drawable/ic_lock_24dp.xml
Normal file
11
V2rayNG/app/src/main/res/drawable/ic_lock_24dp.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M742.4,409.6L716.8,409.6L716.8,332.8C716.8,205.8 613.4,102.4 486.4,102.4S256,205.8 256,332.8L256,409.6h-25.6C188.1,409.6 153.6,444.1 153.6,486.4v409.6c0,42.3 34.5,76.8 76.8,76.8h512c42.3,0 76.8,-34.5 76.8,-76.8v-409.6c0,-42.3 -34.5,-76.8 -76.8,-76.8zM307.2,332.8C307.2,234 387.6,153.6 486.4,153.6S665.6,234 665.6,332.8L665.6,409.6L307.2,409.6L307.2,332.8zM768,896a25.6,25.6 0,0 1,-25.6 25.6h-512a25.6,25.6 0,0 1,-25.6 -25.6v-409.6a25.6,25.6 0,0 1,25.6 -25.6h512a25.6,25.6 0,0 1,25.6 25.6v409.6z" />
|
||||
|
||||
</vector>
|
||||
9
V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml
Normal file
9
V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M740,161a112.1,112.1 0,0 0,-33.5 218.9v95.9L320,602.4L320,318.1a112.1,112.1 0,1 0,-148 -106.1c0,49.3 31.8,91 76,106.1v387.9a112.1,112.1 0,1 0,148 106.1c0,-49.2 -31.8,-91 -76,-106.1v-27.8l423.5,-138.7a50.6,50.6 0,0 0,34.9 -48.2L778.4,378.2c42.9,-15.8 73.6,-57 73.6,-105.2 0,-61.8 -50.2,-112 -112,-112zM236,212a48,48 0,1 1,96 0,48 48,0 0,1 -96,0zM332,812a48,48 0,1 1,-96 0,48 48,0 0,1 96,0zM740,321a48,48 0,1 1,0 -96,48 48,0 0,1 0,96z" />
|
||||
</vector>
|
||||
@@ -23,7 +23,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -34,7 +34,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@@ -46,7 +46,7 @@
|
||||
android:id="@+id/tv_backup_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:maxLines="4"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -73,7 +73,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_configuration_share"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -87,7 +87,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -97,7 +97,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_configuration_restore"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -108,7 +108,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="top"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp">
|
||||
android:paddingTop="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_soure_ccode"
|
||||
@@ -119,7 +119,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -129,7 +129,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_source_code"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -143,7 +143,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -153,7 +153,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_pref_feedback"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -168,7 +168,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -178,7 +178,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_tg_channel"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -192,7 +192,7 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -202,7 +202,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_privacy_policy"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -212,7 +212,7 @@
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_version"
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp">
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -43,7 +43,7 @@
|
||||
android:id="@+id/switch_per_app_proxy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp" />
|
||||
android:paddingStart="@dimen/padding_start" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
android:id="@+id/switch_bypass_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp" />
|
||||
android:paddingStart="@dimen/padding_start" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
tools:context=".ui.LogcatActivity">
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:minLines="1"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/connection_test_pending"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginBottom="24dp">
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
|
||||
187
V2rayNG/app/src/main/res/layout/activity_routing_edit.xml
Normal file
187
V2rayNG/app/src/main/res/layout/activity_routing_edit.xml
Normal file
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.SubSettingActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_locked"
|
||||
android:text="@string/routing_settings_locked"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_rule_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_domain" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_domain"
|
||||
android:layout_width="match_parent"
|
||||
android:maxLines="10"
|
||||
android:hint="@string/routing_settings_tips"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_ip" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_ip"
|
||||
android:maxLines="10"
|
||||
android:hint="@string/routing_settings_tips"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_port" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_protocol" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_protocol"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="@string/routing_settings_protocol_tip"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_network" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_network"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="@string/routing_settings_network_tip"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_outbound_tag" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_outbound_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/outbound_tag" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
70
V2rayNG/app/src/main/res/layout/activity_routing_setting.xml
Normal file
70
V2rayNG/app/src/main/res/layout/activity_routing_setting.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".ui.RoutingSettingActivity">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_domain_strategy" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_domain_strategy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/routing_domain_strategy" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_rule_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_recycler_routing_setting" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</RelativeLayout>
|
||||
@@ -1,16 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tablayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.ServerActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_obfs_password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_obfs_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_id3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/layout_tls_hysteria2" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -117,102 +51,15 @@
|
||||
android:entries="@array/ss_securitys" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
<include layout="@layout/layout_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_tls" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_network" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_network"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/networks" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sp_header_type_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_head_type" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_header_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_request_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_request_host" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_request_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_path"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_path" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/tls_layout" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -118,12 +52,11 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -98,101 +32,15 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
<include layout="@layout/layout_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_tls" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_network" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_network"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/networks" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sp_header_type_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_head_type" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_header_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_request_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_request_host" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_request_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_path"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_path" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/tls_layout" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -127,7 +61,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_encryption" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_security"
|
||||
android:layout_width="match_parent"
|
||||
@@ -137,101 +70,15 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
<include layout="@layout/layout_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_tls" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_network" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_network"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/networks" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sp_header_type_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_head_type" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_header_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_request_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_request_host" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_request_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_path"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_path" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/tls_layout" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -136,101 +70,15 @@
|
||||
android:entries="@array/securitys" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
<include layout="@layout/layout_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_tls" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_network" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_network"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/networks" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sp_header_type_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_head_type" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_header_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_request_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_request_host" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_request_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_path"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_path" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/tls_layout" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -194,8 +128,8 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -51,21 +51,64 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_url" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="10"
|
||||
android:minLines="2"
|
||||
android:scrollbars="vertical"
|
||||
tools:ignore="TextFields" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_filter" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_filter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:text="@string/sub_setting_enable" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="6dp" />
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -82,12 +125,12 @@
|
||||
android:text="@string/sub_auto_update" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/auto_update_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="6dp" />
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -98,25 +141,38 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_url" />
|
||||
|
||||
android:text="@string/sub_setting_pre_profile" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_url"
|
||||
android:id="@+id/et_pre_profile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="10"
|
||||
android:minLines="5"
|
||||
android:scrollbars="vertical" />
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text"
|
||||
android:hint="@string/sub_setting_pre_profile_tip" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_next_profile" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_next_profile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text"
|
||||
android:hint="@string/sub_setting_pre_profile_tip" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".ui.TaskerActivity">
|
||||
|
||||
<RelativeLayout
|
||||
|
||||
@@ -63,11 +63,10 @@
|
||||
android:id="@+id/et_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="10"
|
||||
android:minLines="5"
|
||||
android:minLines="2"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.v2ray.ang.ui.RoutingSettingsFragment">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/routing_settings_tips"
|
||||
android:textColor="@color/color_secondary" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_routing_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="1000"
|
||||
android:minLines="20"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -11,10 +11,8 @@
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingRight="10dp" />
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
@@ -22,8 +20,8 @@
|
||||
android:layout_weight="1.0"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_margin_right_height"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
@@ -44,9 +42,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingRight="6dp" />
|
||||
android:paddingStart="@dimen/padding_start"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -12,7 +12,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="5dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
app:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -80,7 +80,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="5dp">
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_subscription"
|
||||
@@ -172,7 +172,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="5dp">
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_type"
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_cardview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:nextFocusRight="@+id/layout_edit"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/remarks"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_locked"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="@dimen/padding_start"
|
||||
android:layout_width="@dimen/padding"
|
||||
android:layout_height="@dimen/padding"
|
||||
app:srcCompat="@drawable/ic_lock_24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domainIp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/outboundTag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:gravity="center"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:nextFocusLeft="@+id/info_container"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_spacing">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
android:layout_height="@dimen/png_height"
|
||||
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
@@ -6,18 +6,19 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_cardview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
@@ -26,19 +27,12 @@
|
||||
android:nextFocusRight="@+id/layout_edit"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chk_enable"
|
||||
android:layout_width="6dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
@@ -50,16 +44,29 @@
|
||||
android:id="@+id/tv_url"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_enable"
|
||||
android:text="@string/sub_setting_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
app:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -24,7 +24,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
75
V2rayNG/app/src/main/res/layout/layout_address_port.xml
Normal file
75
V2rayNG/app/src/main/res/layout/layout_address_port.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_server"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_address3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_port3" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,7 +5,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="@dimen/padding">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pb_waiting"
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l1"
|
||||
android:id="@+id/lay_stream_security"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
@@ -28,7 +28,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l2"
|
||||
android:id="@+id/lay_sni"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
@@ -49,7 +49,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l3"
|
||||
android:id="@+id/lay_stream_fingerprint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@@ -70,7 +70,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l4"
|
||||
android:id="@+id/lay_stream_alpn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@@ -91,7 +91,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l5"
|
||||
android:id="@+id/lay_allow_insecure"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
@@ -109,7 +109,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l6"
|
||||
android:id="@+id/lay_public_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
@@ -130,7 +130,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l7"
|
||||
android:id="@+id/lay_short_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
@@ -151,7 +151,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l8"
|
||||
android:id="@+id/lay_spider_x"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user