Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c54841d2 | ||
|
|
54fa356999 | ||
|
|
9642b7f64f | ||
|
|
dd2d2c1638 | ||
|
|
e21950dbcd | ||
|
|
d016ab06d4 | ||
|
|
4d9aced5a4 | ||
|
|
62b928e6a0 | ||
|
|
0ce60eae73 | ||
|
|
5930a6a9eb | ||
|
|
a360310be2 | ||
|
|
820e6cdf36 | ||
|
|
658b890325 | ||
|
|
fb017c6659 | ||
|
|
00e6314afe | ||
|
|
463f45804f | ||
|
|
572955dd1e | ||
|
|
375a209beb | ||
|
|
872f9ce199 | ||
|
|
b4f02c9bd6 | ||
|
|
e567719f5b | ||
|
|
8407fc5825 | ||
|
|
a3e49dcc3d | ||
|
|
7b47bbe99a | ||
|
|
0fb2165015 |
@@ -25,12 +25,14 @@ android {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +69,6 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
testImplementation 'junit:junit:4.13'
|
||||
implementation project(':dpreference')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||
@@ -89,6 +90,7 @@ dependencies {
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
|
||||
// DSL
|
||||
implementation 'com.tencent:mmkv-static:1.0.19'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'io.reactivex:rxjava:1.3.4'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
|
||||
@@ -60,13 +60,7 @@
|
||||
android:name=".ui.ServerActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:name=".ui.Server2Activity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:name=".ui.Server3Activity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:name=".ui.Server4Activity"
|
||||
android:name=".ui.ServerCustomConfigActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity android:name=".ui.SettingsActivity" />
|
||||
<activity android:name=".ui.PerAppProxyActivity" />
|
||||
|
||||
@@ -2,8 +2,7 @@ package com.v2ray.ang
|
||||
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import me.dozen.dpreference.DPreference
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
class AngApplication : MultiDexApplication() {
|
||||
companion object {
|
||||
@@ -14,8 +13,6 @@ class AngApplication : MultiDexApplication() {
|
||||
var firstRun = false
|
||||
private set
|
||||
|
||||
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -27,6 +24,6 @@ class AngApplication : MultiDexApplication() {
|
||||
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
||||
|
||||
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
||||
AngConfigManager.inject(this)
|
||||
MMKV.initialize(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,43 @@ package com.v2ray.ang
|
||||
*/
|
||||
object AppConfig {
|
||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_CURR_CONFIG = "pref_v2ray_config"
|
||||
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
|
||||
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
|
||||
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
|
||||
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
|
||||
const val VMESS_PROTOCOL: String = "vmess://"
|
||||
const val SS_PROTOCOL: String = "ss://"
|
||||
const val SOCKS_PROTOCOL: String = "socks://"
|
||||
// legacy
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
|
||||
// Preferences mapped to MMKV
|
||||
const val PREF_MODE = "pref_mode"
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||
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_PER_APP_PROXY = "pref_per_app_proxy"
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
||||
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
||||
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
||||
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
// const val PREF_HTTP_PORT = "pref_http_port"
|
||||
// const val PREF_DONATE = "pref_donate"
|
||||
// const val PREF_LICENSES = "pref_licenses"
|
||||
// const val PREF_FEEDBACK = "pref_feedback"
|
||||
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||
|
||||
const val HTTP_PROTOCOL: String = "http://"
|
||||
const val HTTPS_PROTOCOL: String = "https://"
|
||||
|
||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||
@@ -28,9 +53,6 @@ object AppConfig {
|
||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||
const val TASKER_DEFAULT_GUID = "Default"
|
||||
|
||||
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 TAG_AGENT = "proxy"
|
||||
const val TAG_DIRECT = "direct"
|
||||
const val TAG_BLOCKED = "block"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
enum class EConfigType(val value: Int) {
|
||||
VMESS(1),
|
||||
CUSTOM(2),
|
||||
SHADOWSOCKS(3),
|
||||
SOCKS(4);
|
||||
enum class EConfigType(val value: Int, val protocolScheme: String) {
|
||||
VMESS(1, "vmess://"),
|
||||
CUSTOM(2, ""),
|
||||
SHADOWSOCKS(3, "ss://"),
|
||||
SOCKS(4, "socks://"),
|
||||
VLESS(5, "vless://"),
|
||||
TROJAN(6, "trojan://");
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
|
||||
fun getTestDelayString(): String {
|
||||
if (testDelayMillis == 0L) {
|
||||
return ""
|
||||
}
|
||||
return testDelayMillis.toString() + "ms"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
import com.v2ray.ang.AppConfig.TAG_AGENT
|
||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
data class ServerConfig(
|
||||
val configVersion: Int = 3,
|
||||
val configType: EConfigType,
|
||||
var subscriptionId: String = "",
|
||||
val addedTime: Long = System.currentTimeMillis(),
|
||||
var remarks: String = "",
|
||||
val outboundBean: V2rayConfig.OutboundBean? = null,
|
||||
var fullConfig: V2rayConfig? = null
|
||||
) {
|
||||
companion object {
|
||||
fun create(configType: EConfigType): ServerConfig {
|
||||
when(configType) {
|
||||
EConfigType.VMESS, EConfigType.VLESS ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.toLowerCase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
EConfigType.CUSTOM ->
|
||||
return ServerConfig(configType = configType)
|
||||
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.toLowerCase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getProxyOutbound(): V2rayConfig.OutboundBean? {
|
||||
if (configType != EConfigType.CUSTOM) {
|
||||
return outboundBean
|
||||
}
|
||||
return fullConfig?.getProxyOutbound()
|
||||
}
|
||||
|
||||
fun getAllOutboundTags(): MutableList<String> {
|
||||
if (configType != EConfigType.CUSTOM) {
|
||||
return mutableListOf(TAG_AGENT, TAG_DIRECT, TAG_BLOCKED)
|
||||
}
|
||||
fullConfig?.let { config ->
|
||||
return config.outbounds.map { it.tag }.toMutableList()
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
fun getV2rayPointDomainAndPort(): String {
|
||||
val address = getProxyOutbound()?.getServerAddress().orEmpty()
|
||||
val port = getProxyOutbound()?.getServerPort()
|
||||
return if (Utils.isIpv6Address(address)) {
|
||||
String.format("[%s]:%s", address, port)
|
||||
} else {
|
||||
String.format("%s:%s", address, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class SubscriptionItem(
|
||||
var remarks: String = "",
|
||||
var url: String = "",
|
||||
var enabled: Boolean = true,
|
||||
val addedTime: Long = System.currentTimeMillis()) {
|
||||
}
|
||||
@@ -1,159 +1,443 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.lang.reflect.Type
|
||||
|
||||
data class V2rayConfig(
|
||||
val stats: Any?=null,
|
||||
var stats: Any? = null,
|
||||
val log: LogBean,
|
||||
val policy: PolicyBean,
|
||||
var policy: PolicyBean?,
|
||||
val inbounds: ArrayList<InboundBean>,
|
||||
var outbounds: ArrayList<OutboundBean>,
|
||||
var dns: DnsBean,
|
||||
val routing: RoutingBean) {
|
||||
val routing: RoutingBean,
|
||||
val api: Any? = null,
|
||||
val transport: Any? = null,
|
||||
val reverse: Any? = null,
|
||||
val fakedns: Any? = null,
|
||||
val browserForwarder: Any? = null) {
|
||||
companion object {
|
||||
const val DEFAULT_PORT = 443
|
||||
const val DEFAULT_SECURITY = "auto"
|
||||
const val DEFAULT_LEVEL = 8
|
||||
const val DEFAULT_NETWORK = "tcp"
|
||||
const val DEFAULT_FLOW = "xtls-rprx-splice"
|
||||
|
||||
const val TLS = "tls"
|
||||
const val XTLS = "xtls"
|
||||
const val HTTP = "http"
|
||||
}
|
||||
|
||||
data class LogBean(val access: String,
|
||||
val error: String,
|
||||
val loglevel: String)
|
||||
var loglevel: String?,
|
||||
val dnsLog: Boolean? = null)
|
||||
|
||||
data class InboundBean(
|
||||
var tag: String,
|
||||
var port: Int,
|
||||
var protocol: String,
|
||||
var listen: String?=null,
|
||||
val settings: InSettingsBean,
|
||||
val sniffing: SniffingBean?) {
|
||||
var listen: String? = null,
|
||||
val settings: Any? = null,
|
||||
val sniffing: SniffingBean?,
|
||||
val streamSettings: Any? = null,
|
||||
val allocate: Any? = null) {
|
||||
|
||||
data class InSettingsBean(val auth: String? = null,
|
||||
val udp: Boolean? = null,
|
||||
val userLevel: Int? =null,
|
||||
val userLevel: Int? = null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
val network: String? = null)
|
||||
|
||||
data class SniffingBean(var enabled: Boolean,
|
||||
val destOverride: List<String>)
|
||||
val destOverride: List<String>,
|
||||
val metadataOnly: Boolean? = null)
|
||||
}
|
||||
|
||||
data class OutboundBean(val tag: String,
|
||||
data class OutboundBean(val tag: String = "proxy",
|
||||
var protocol: String,
|
||||
var settings: OutSettingsBean?,
|
||||
var streamSettings: StreamSettingsBean?,
|
||||
var mux: MuxBean?) {
|
||||
var settings: OutSettingsBean? = null,
|
||||
var streamSettings: StreamSettingsBean? = null,
|
||||
val proxySettings: Any? = null,
|
||||
val sendThrough: String? = null,
|
||||
val mux: MuxBean? = MuxBean(false)) {
|
||||
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>?,
|
||||
var servers: List<ServersBean>?,
|
||||
var response: Response) {
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
|
||||
var servers: List<ServersBean>? = null,
|
||||
/*Blackhole*/
|
||||
var response: Response? = null,
|
||||
/*DNS*/
|
||||
val network: String? = null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
/*Freedom*/
|
||||
val domainStrategy: String? = null,
|
||||
val redirect: String? = null,
|
||||
val userLevel: Int? = null,
|
||||
/*Loopback*/
|
||||
val inboundTag: String? = null) {
|
||||
|
||||
data class VnextBean(var address: String,
|
||||
var port: Int,
|
||||
data class VnextBean(var address: String = "",
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var users: List<UsersBean>) {
|
||||
|
||||
data class UsersBean(var id: String,
|
||||
var alterId: Int,
|
||||
var security: String,
|
||||
var level: Int)
|
||||
data class UsersBean(var id: String = "",
|
||||
var alterId: Int? = null,
|
||||
var security: String = DEFAULT_SECURITY,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
var encryption: String = "",
|
||||
var flow: String = "")
|
||||
}
|
||||
|
||||
data class ServersBean(var address: String,
|
||||
var method: String,
|
||||
var ota: Boolean,
|
||||
var password: String,
|
||||
var port: Int,
|
||||
var level: Int)
|
||||
data class ServersBean(var address: String = "",
|
||||
var method: String = "chacha20-poly1305",
|
||||
var ota: Boolean = false,
|
||||
var password: String = "",
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
val email: String? = null,
|
||||
val flow: String? = null,
|
||||
val ivCheck: Boolean? = null,
|
||||
var users: List<SocksUsersBean>? = null) {
|
||||
|
||||
|
||||
data class SocksUsersBean(var user: String = "",
|
||||
var pass: String = "",
|
||||
var level: Int = DEFAULT_LEVEL)
|
||||
}
|
||||
|
||||
data class Response(var type: String)
|
||||
}
|
||||
|
||||
data class StreamSettingsBean(var network: String,
|
||||
var security: String,
|
||||
var tcpSettings: TcpSettingsBean?,
|
||||
var kcpSettings: KcpSettingsBean?,
|
||||
var wsSettings: WsSettingsBean?,
|
||||
var httpSettings: HttpSettingsBean?,
|
||||
var tlsSettings: TlsSettingsBean?,
|
||||
var quicSettings: QuicSettingBean?
|
||||
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
|
||||
var security: String = "",
|
||||
var tcpSettings: TcpSettingsBean? = null,
|
||||
var kcpSettings: KcpSettingsBean? = null,
|
||||
var wsSettings: WsSettingsBean? = null,
|
||||
var httpSettings: HttpSettingsBean? = null,
|
||||
var tlsSettings: TlsSettingsBean? = null,
|
||||
var quicSettings: QuicSettingBean? = null,
|
||||
var xtlsSettings: TlsSettingsBean? = null,
|
||||
var grpcSettings: GrpcSettingsBean? = null,
|
||||
val dsSettings: Any? = null,
|
||||
val sockopt: Any? = null
|
||||
) {
|
||||
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
data class HeaderBean(var type: String = "none",
|
||||
var request: Any? = null,
|
||||
var response: Any? = null)
|
||||
var request: RequestBean? = null,
|
||||
var response: Any? = null) {
|
||||
data class RequestBean(var path: List<String> = ArrayList(),
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val version: String? = null,
|
||||
val method: String? = null) {
|
||||
data class HeadersBean(var Host: List<String> = ArrayList(),
|
||||
@SerializedName("User-Agent")
|
||||
val userAgent: List<String>? = null,
|
||||
@SerializedName("Accept-Encoding")
|
||||
val acceptEncoding: List<String>? = null,
|
||||
val Connection: List<String>? = null,
|
||||
val Pragma: String? = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class KcpSettingsBean(var mtu: Int = 1350,
|
||||
var tti: Int = 20,
|
||||
var tti: Int = 50,
|
||||
var uplinkCapacity: Int = 12,
|
||||
var downlinkCapacity: Int = 100,
|
||||
var congestion: Boolean = false,
|
||||
var readBufferSize: Int = 1,
|
||||
var writeBufferSize: Int = 1,
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
var header: HeaderBean = HeaderBean(),
|
||||
var seed: String? = null) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class WsSettingsBean(var path: String = "",
|
||||
var headers: HeadersBean = HeadersBean()) {
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val maxEarlyData: Int? = null,
|
||||
val useBrowserForwarding: Boolean? = null,
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
|
||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
||||
var path: String = "")
|
||||
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = true,
|
||||
var serverName: String = "")
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = false,
|
||||
var serverName: String = "",
|
||||
val alpn: List<String>? = null,
|
||||
val minVersion: String? = null,
|
||||
val maxVersion: String? = null,
|
||||
val preferServerCipherSuites: Boolean? = null,
|
||||
val cipherSuites: String? = null,
|
||||
val fingerprint: String? = null,
|
||||
val certificates: List<Any>? = null,
|
||||
val disableSystemRoot: Boolean? = null,
|
||||
val enableSessionResumption: Boolean? = null)
|
||||
|
||||
data class QuicSettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class GrpcSettingsBean(var serviceName: String = "",
|
||||
val multiMode: Boolean? = null)
|
||||
|
||||
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||
quicSecurity: String?, key: String?): String {
|
||||
var sni = ""
|
||||
network = transport
|
||||
when (network) {
|
||||
"tcp" -> {
|
||||
val tcpSetting = TcpSettingsBean()
|
||||
if (headerType == HTTP) {
|
||||
tcpSetting.header.type = HTTP
|
||||
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
||||
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
||||
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }
|
||||
requestObj.path = (path ?: "").split(",").map { it.trim() }
|
||||
tcpSetting.header.request = requestObj
|
||||
sni = requestObj.headers.Host.getOrNull(0) ?: sni
|
||||
}
|
||||
} else {
|
||||
tcpSetting.header.type = "none"
|
||||
sni = host ?: ""
|
||||
}
|
||||
tcpSettings = tcpSetting
|
||||
}
|
||||
"kcp" -> {
|
||||
val kcpsetting = KcpSettingsBean()
|
||||
kcpsetting.header.type = headerType ?: "none"
|
||||
if (seed.isNullOrEmpty()) {
|
||||
kcpsetting.seed = null
|
||||
} else {
|
||||
kcpsetting.seed = seed
|
||||
}
|
||||
kcpSettings = kcpsetting
|
||||
}
|
||||
"ws" -> {
|
||||
val wssetting = WsSettingsBean()
|
||||
wssetting.headers.Host = host ?: ""
|
||||
sni = wssetting.headers.Host
|
||||
wssetting.path = path ?: "/"
|
||||
wsSettings = wssetting
|
||||
}
|
||||
"h2", "http" -> {
|
||||
network = "h2"
|
||||
val h2Setting = HttpSettingsBean()
|
||||
h2Setting.host = (host ?: "").split(",").map { it.trim() }
|
||||
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||
h2Setting.path = path ?: "/"
|
||||
httpSettings = h2Setting
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsetting = QuicSettingBean()
|
||||
quicsetting.security = quicSecurity ?: "none"
|
||||
quicsetting.key = key ?: ""
|
||||
quicsetting.header.type = headerType ?: "none"
|
||||
quicSettings = quicsetting
|
||||
}
|
||||
"grpc" -> {
|
||||
val grpcSetting = GrpcSettingsBean()
|
||||
grpcSetting.serviceName = path ?: ""
|
||||
sni = host ?: ""
|
||||
grpcSettings = grpcSetting
|
||||
}
|
||||
}
|
||||
return sni
|
||||
}
|
||||
|
||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String) {
|
||||
security = streamSecurity
|
||||
val tlsSetting = TlsSettingsBean(
|
||||
allowInsecure = allowInsecure,
|
||||
serverName = sni
|
||||
)
|
||||
if (security == TLS) {
|
||||
tlsSettings = tlsSetting
|
||||
} else if (security == XTLS) {
|
||||
xtlsSettings = tlsSetting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean)
|
||||
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
|
||||
|
||||
fun getServerAddress(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
return settings?.vnext?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return settings?.servers?.get(0)?.address
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getServerPort(): Int? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
return settings?.vnext?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return settings?.servers?.get(0)?.port
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getPassword(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return settings?.servers?.get(0)?.password
|
||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
|
||||
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getSecurityEncryption(): String? {
|
||||
return when {
|
||||
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.security
|
||||
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.encryption
|
||||
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.get(0)?.method
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getTransportSettingDetails(): List<String>? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
val transport = streamSettings?.network ?: return null
|
||||
return when (transport) {
|
||||
"tcp" -> {
|
||||
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
||||
listOf(tcpSetting.header.type,
|
||||
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
|
||||
tcpSetting.header.request?.path?.joinToString().orEmpty())
|
||||
}
|
||||
"kcp" -> {
|
||||
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
||||
listOf(kcpSetting.header.type,
|
||||
"",
|
||||
kcpSetting.seed.orEmpty())
|
||||
}
|
||||
"ws" -> {
|
||||
val wsSetting = streamSettings?.wsSettings ?: return null
|
||||
listOf("",
|
||||
wsSetting.headers.Host,
|
||||
wsSetting.path)
|
||||
}
|
||||
"h2" -> {
|
||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||
listOf("",
|
||||
h2Setting.host.joinToString(),
|
||||
h2Setting.path)
|
||||
}
|
||||
"quic" -> {
|
||||
val quicSetting = streamSettings?.quicSettings ?: return null
|
||||
listOf(quicSetting.header.type,
|
||||
quicSetting.security,
|
||||
quicSetting.key)
|
||||
}
|
||||
"grpc" -> {
|
||||
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
||||
listOf("",
|
||||
"",
|
||||
grpcSetting.serviceName)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
//data class DnsBean(var servers: List<String>)
|
||||
data class DnsBean(var servers: List<Any>?=null,
|
||||
var hosts: Map<String, String>?=null
|
||||
data class DnsBean(var servers: List<Any>? = null,
|
||||
var hosts: Map<String, String>? = null,
|
||||
val clientIp: String? = null,
|
||||
val disableCache: Boolean? = null,
|
||||
val queryStrategy: String? = null,
|
||||
val tag: String? = null
|
||||
) {
|
||||
data class ServersBean(var address: String = "",
|
||||
var port: Int = 0,
|
||||
var domains: List<String>?)
|
||||
var domains: List<String>?,
|
||||
var expectIPs: List<String>?,
|
||||
val clientIp: String? = null)
|
||||
}
|
||||
|
||||
data class RoutingBean(var domainStrategy: String,
|
||||
var rules: ArrayList<RulesBean>) {
|
||||
val domainMatcher: String? = null,
|
||||
var rules: ArrayList<RulesBean>,
|
||||
val balancers: List<Any>? = null) {
|
||||
|
||||
data class RulesBean(var type: String = "",
|
||||
var ip: ArrayList<String>? = null,
|
||||
var domain: ArrayList<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
var balancerTag: String? = null,
|
||||
var port: String? = null,
|
||||
var inboundTag: ArrayList<String>? = null)
|
||||
val sourcePort: String? = null,
|
||||
val network: String? = null,
|
||||
val source: List<String>? = null,
|
||||
val user: List<String>? = null,
|
||||
var inboundTag: List<String>? = null,
|
||||
val protocol: List<String>? = null,
|
||||
val attrs: String? = null,
|
||||
val domainMatcher: String? = null
|
||||
)
|
||||
}
|
||||
|
||||
data class PolicyBean(var levels: Map<String, LevelBean>,
|
||||
var system: Any?=null) {
|
||||
var system: Any? = null) {
|
||||
data class LevelBean(
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
var uplinkOnly: Int? = null,
|
||||
var downlinkOnly: Int? = null)
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
var uplinkOnly: Int? = null,
|
||||
var downlinkOnly: Int? = null,
|
||||
val statsUserUplink: Boolean? = null,
|
||||
val statsUserDownlink: Boolean? = null,
|
||||
var bufferSize: Int? = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun getProxyOutbound(): OutboundBean? {
|
||||
outbounds.forEach { outbound ->
|
||||
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.VLESS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.SOCKS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return outbound
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun toPrettyPrinting(): String {
|
||||
return 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()
|
||||
.toJson(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ data class VmessQRCode(var v: String = "",
|
||||
var type: String = "",
|
||||
var host: String = "",
|
||||
var path: String = "",
|
||||
var tls: String = "")
|
||||
var tls: String = "",
|
||||
var sni: String = "")
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import me.dozen.dpreference.DPreference
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import org.json.JSONObject
|
||||
import java.net.URLConnection
|
||||
@@ -16,13 +15,6 @@ import java.net.URLConnection
|
||||
val Context.v2RayApplication: AngApplication
|
||||
get() = applicationContext as AngApplication
|
||||
|
||||
// Usage note: DPreference use Android ContentProvider to redirect multi process access to main process.
|
||||
// Currently, RunSoLibV2RayDaemon process will run proxy core, keep minimum configuration and long running
|
||||
// in the background, support toggle on/off. That means it should NOT use DPreference after the initial
|
||||
// creation and setup of the service
|
||||
val Context.defaultDPreference: DPreference
|
||||
get() = v2RayApplication.defaultDPreference
|
||||
|
||||
inline fun Context.toast(message: Int): Toast = ToastCompat
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
@@ -35,7 +27,7 @@ inline fun Context.toast(message: CharSequence): Toast = ToastCompat
|
||||
show()
|
||||
}
|
||||
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)!!
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
|
||||
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
|
||||
|
||||
const val threshold = 1000
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.v2ray.ang.extension
|
||||
|
||||
import android.preference.Preference
|
||||
|
||||
fun Preference.onClick(listener: () -> Unit) {
|
||||
setOnPreferenceClickListener {
|
||||
listener()
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,16 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import com.google.zxing.WriterException
|
||||
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?) {
|
||||
|
||||
try {
|
||||
@@ -23,7 +28,8 @@ class TaskerReceiver : BroadcastReceiver() {
|
||||
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
||||
Utils.startVServiceFromToggle(context)
|
||||
} else {
|
||||
Utils.startVService(context, guid)
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
V2RayServiceManager.startV2Ray(context)
|
||||
}
|
||||
} else {
|
||||
Utils.stopVService(context)
|
||||
|
||||
@@ -25,7 +25,7 @@ class QSTileService : TileService() {
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
|
||||
} else if (state == Tile.STATE_ACTIVE) {
|
||||
qsTile?.state = Tile.STATE_ACTIVE
|
||||
qsTile?.label = V2RayServiceManager.currentConfigName
|
||||
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,18 @@ import android.os.Build
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import go.Seq
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayPoint
|
||||
@@ -41,6 +42,8 @@ object V2RayServiceManager {
|
||||
|
||||
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
|
||||
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) {
|
||||
@@ -52,7 +55,7 @@ object V2RayServiceManager {
|
||||
Seq.setContext(context)
|
||||
}
|
||||
}
|
||||
var currentConfigName = "NG"
|
||||
var currentConfig: ServerConfig? = null
|
||||
|
||||
private var lastQueryTime = 0L
|
||||
private var mBuilder: NotificationCompat.Builder? = null
|
||||
@@ -60,12 +63,12 @@ object V2RayServiceManager {
|
||||
private var mNotificationManager: NotificationManager? = null
|
||||
|
||||
fun startV2Ray(context: Context) {
|
||||
if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
|
||||
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
||||
}else{
|
||||
context.toast(R.string.toast_services_start)
|
||||
}
|
||||
val intent = if (context.v2RayApplication.defaultDPreference.getPrefString(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)
|
||||
@@ -123,7 +126,12 @@ object V2RayServiceManager {
|
||||
|
||||
fun startV2rayPoint() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||
if (!v2rayPoint.isRunning) {
|
||||
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
|
||||
if (!result.status)
|
||||
return
|
||||
|
||||
try {
|
||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
@@ -135,12 +143,12 @@ object V2RayServiceManager {
|
||||
Log.d(service.packageName, e.toString())
|
||||
}
|
||||
|
||||
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
|
||||
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
|
||||
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN"
|
||||
currentConfigName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
|
||||
v2rayPoint.configureFileContent = result.content
|
||||
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
||||
currentConfig = config
|
||||
v2rayPoint.enableLocalDNS = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
|
||||
v2rayPoint.forwardIpv6 = settingsStorage?.decodeBool(AppConfig.PREF_FORWARD_IPV6) ?: false
|
||||
v2rayPoint.proxyOnly = settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" != "VPN"
|
||||
|
||||
try {
|
||||
v2rayPoint.runLoop()
|
||||
@@ -244,7 +252,7 @@ object V2RayServiceManager {
|
||||
|
||||
mBuilder = NotificationCompat.Builder(service, channelId)
|
||||
.setSmallIcon(R.drawable.ic_v)
|
||||
.setContentTitle(currentConfigName)
|
||||
.setContentTitle(currentConfig?.remarks)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setOngoing(true)
|
||||
.setShowWhen(false)
|
||||
@@ -281,7 +289,7 @@ object V2RayServiceManager {
|
||||
mSubscription = null
|
||||
}
|
||||
|
||||
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
|
||||
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
||||
if (mBuilder != null) {
|
||||
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_v)
|
||||
@@ -305,13 +313,12 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
fun startSpeedNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (mSubscription == null &&
|
||||
v2rayPoint.isRunning &&
|
||||
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
||||
var lastZeroSpeed = false
|
||||
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
|
||||
outboundTags.remove(TAG_DIRECT)
|
||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||
outboundTags?.remove(TAG_DIRECT)
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
@@ -319,7 +326,7 @@ object V2RayServiceManager {
|
||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags.forEach {
|
||||
outboundTags?.forEach {
|
||||
val up = v2rayPoint.queryStats(it, "uplink")
|
||||
val down = v2rayPoint.queryStats(it, "downlink")
|
||||
if (up + down > 0) {
|
||||
@@ -332,7 +339,7 @@ object V2RayServiceManager {
|
||||
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
|
||||
if (!zeroSpeed || !lastZeroSpeed) {
|
||||
if (proxyTotal == 0L) {
|
||||
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
|
||||
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
|
||||
}
|
||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds)
|
||||
@@ -358,7 +365,7 @@ object V2RayServiceManager {
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
updateNotification(currentConfigName, 0, 0)
|
||||
updateNotification(currentConfig?.remarks, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.ui.PerAppProxyActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -22,6 +22,8 @@ import java.io.File
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private lateinit var mInterface: ParcelFileDescriptor
|
||||
|
||||
/**
|
||||
@@ -91,8 +93,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
// If the old interface has exactly the same parameters, use it!
|
||||
// Configure a builder while parsing the parameters.
|
||||
val builder = Builder()
|
||||
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
|
||||
parameters.split(" ")
|
||||
.map { it.split(",") }
|
||||
@@ -120,18 +122,18 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
if(!enableLocalDns) {
|
||||
Utils.getRemoteDnsServers(defaultDPreference)
|
||||
Utils.getRemoteDnsServers()
|
||||
.forEach {
|
||||
builder.addDnsServer(it)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setSession(V2RayServiceManager.currentConfigName)
|
||||
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
|
||||
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null)
|
||||
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false)
|
||||
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
|
||||
apps?.forEach {
|
||||
try {
|
||||
if (bypassApps)
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.app.FragmentManager
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.NavigationView
|
||||
import android.support.v4.view.GravityCompat
|
||||
import android.support.v4.widget.DrawerLayout
|
||||
import android.support.v7.app.ActionBarDrawerToggle
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import com.v2ray.ang.R
|
||||
|
||||
abstract class BaseDrawerActivity : BaseActivity() {
|
||||
companion object {
|
||||
|
||||
private val TAG = "BaseDrawerActivity"
|
||||
}
|
||||
|
||||
private var mToolbar: Toolbar? = null
|
||||
|
||||
private var mDrawerToggle: ActionBarDrawerToggle? = null
|
||||
|
||||
private var mDrawerLayout: DrawerLayout? = null
|
||||
|
||||
private var mToolbarInitialized: Boolean = false
|
||||
|
||||
private var mItemToOpenWhenDrawerCloses = -1
|
||||
|
||||
private val backStackChangedListener = FragmentManager.OnBackStackChangedListener { updateDrawerToggle() }
|
||||
|
||||
private val drawerListener = object : DrawerLayout.DrawerListener {
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
mDrawerToggle!!.onDrawerSlide(drawerView, slideOffset)
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
mDrawerToggle!!.onDrawerOpened(drawerView)
|
||||
//supportActionBar!!.setTitle(R.string.app_name)
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
mDrawerToggle!!.onDrawerClosed(drawerView)
|
||||
|
||||
if (mItemToOpenWhenDrawerCloses >= 0) {
|
||||
val extras = ActivityOptions.makeCustomAnimation(
|
||||
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
|
||||
var activityClass: Class<*>? = null
|
||||
when (mItemToOpenWhenDrawerCloses) {
|
||||
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
|
||||
R.id.settings -> activityClass = SettingsActivity::class.java
|
||||
R.id.logcat -> {
|
||||
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
|
||||
return
|
||||
}
|
||||
R.id.donate -> {
|
||||
// startActivity<InappBuyActivity>()
|
||||
return
|
||||
}
|
||||
}
|
||||
if (activityClass != null) {
|
||||
startActivity(Intent(this@BaseDrawerActivity, activityClass), extras)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
mDrawerToggle!!.onDrawerStateChanged(newState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Activity onCreate")
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!mToolbarInitialized) {
|
||||
throw IllegalStateException("You must run super.initializeToolbar at " + "the end of your onCreate method")
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
// Whenever the fragment back stack changes, we may need to update the
|
||||
// action bar toggle: only top level screens show the hamburger-like icon, inner
|
||||
// screens - either Activities or fragments - show the "Up" icon instead.
|
||||
fragmentManager.addOnBackStackChangedListener(backStackChangedListener)
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
super.onPause()
|
||||
fragmentManager.removeOnBackStackChangedListener(backStackChangedListener)
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
mDrawerToggle!!.syncState()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
mDrawerToggle!!.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (mDrawerToggle != null && mDrawerToggle!!.onOptionsItemSelected(item)) {
|
||||
return true
|
||||
}
|
||||
// If not handled by drawerToggle, home needs to be handled by returning to previous
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
// If the drawer is open, back will close it
|
||||
if (mDrawerLayout != null && mDrawerLayout!!.isDrawerOpen(GravityCompat.START)) {
|
||||
mDrawerLayout!!.closeDrawers()
|
||||
return
|
||||
}
|
||||
// Otherwise, it may return to the previous fragment stack
|
||||
val fragmentManager = fragmentManager
|
||||
if (fragmentManager.backStackEntryCount > 0) {
|
||||
fragmentManager.popBackStack()
|
||||
} else {
|
||||
// Lastly, it will rely on the system behavior for back
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDrawerToggle() {
|
||||
if (mDrawerToggle == null) {
|
||||
return
|
||||
}
|
||||
val isRoot = fragmentManager.backStackEntryCount == 0
|
||||
mDrawerToggle!!.isDrawerIndicatorEnabled = isRoot
|
||||
|
||||
supportActionBar!!.setDisplayShowHomeEnabled(!isRoot)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(!isRoot)
|
||||
supportActionBar!!.setHomeButtonEnabled(!isRoot)
|
||||
|
||||
if (isRoot) {
|
||||
mDrawerToggle!!.syncState()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun initializeToolbar() {
|
||||
mToolbar = findViewById<View>(R.id.toolbar) as Toolbar
|
||||
if (mToolbar == null) {
|
||||
throw IllegalStateException("Layout is required to include a Toolbar with id " + "'toolbar'")
|
||||
}
|
||||
|
||||
// mToolbar.inflateMenu(R.menu.main);
|
||||
|
||||
mDrawerLayout = findViewById<View>(R.id.drawer_layout) as DrawerLayout
|
||||
if (mDrawerLayout != null) {
|
||||
val navigationView = findViewById<View>(R.id.nav_view) as NavigationView
|
||||
?: throw IllegalStateException("Layout requires a NavigationView " + "with id 'nav_view'")
|
||||
|
||||
// Create an ActionBarDrawerToggle that will handle opening/closing of the drawer:
|
||||
mDrawerToggle = ActionBarDrawerToggle(this, mDrawerLayout,
|
||||
mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
||||
|
||||
mDrawerLayout!!.addDrawerListener(drawerListener)
|
||||
|
||||
populateDrawerItems(navigationView)
|
||||
setSupportActionBar(mToolbar)
|
||||
updateDrawerToggle()
|
||||
} else {
|
||||
setSupportActionBar(mToolbar)
|
||||
}
|
||||
|
||||
mToolbarInitialized = true
|
||||
}
|
||||
|
||||
private fun populateDrawerItems(navigationView: NavigationView) {
|
||||
navigationView.setNavigationItemSelectedListener { menuItem ->
|
||||
menuItem.isChecked = true
|
||||
mItemToOpenWhenDrawerCloses = menuItem.itemId
|
||||
mDrawerLayout!!.closeDrawers()
|
||||
true
|
||||
}
|
||||
|
||||
if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.sub_setting)
|
||||
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,22 +16,26 @@ import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
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.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import com.v2ray.ang.viewmodel.MainViewModel
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import libv2ray.Libv2ray
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.net.URL
|
||||
@@ -46,8 +50,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
|
||||
val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -58,7 +64,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
fab.setOnClickListener {
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
Utils.stopVService(this)
|
||||
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
|
||||
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
startV2Ray()
|
||||
@@ -95,47 +101,65 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
||||
|
||||
setupViewModelObserver()
|
||||
migrateLegacy()
|
||||
}
|
||||
|
||||
private fun setupViewModelObserver() {
|
||||
mainViewModel.updateListAction.observe(this, {
|
||||
val index = it ?: return@observe
|
||||
if (index >= 0) {
|
||||
adapter.updateSelectedItem(index)
|
||||
adapter.notifyItemChanged(index)
|
||||
} else {
|
||||
adapter.updateConfigList()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
})
|
||||
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
|
||||
mainViewModel.isRunning.observe(this, {
|
||||
val isRunning = it ?: return@observe
|
||||
adapter.changeable = !isRunning
|
||||
adapter.isRunning = isRunning
|
||||
if (isRunning) {
|
||||
fab.setImageResource(R.drawable.ic_v)
|
||||
tv_test_state.text = getString(R.string.connection_connected)
|
||||
layout_test.isFocusable = true
|
||||
} else {
|
||||
fab.setImageResource(R.drawable.ic_v_idle)
|
||||
tv_test_state.text = getString(R.string.connection_not_connected)
|
||||
layout_test.isFocusable = false
|
||||
}
|
||||
hideCircle()
|
||||
})
|
||||
mainViewModel.startListenBroadcast()
|
||||
}
|
||||
|
||||
private fun migrateLegacy() {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
||||
if (result != null) {
|
||||
launch(Dispatchers.Main) {
|
||||
if (result) {
|
||||
toast(getString(R.string.migration_success))
|
||||
mainViewModel.reloadServerList()
|
||||
} else {
|
||||
toast(getString(R.string.migration_fail))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startV2Ray() {
|
||||
if (AngConfigManager.configs.index < 0) {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
showCircle()
|
||||
// toast(R.string.toast_services_start)
|
||||
if (!Utils.startVService(this, AngConfigManager.configs.index)) {
|
||||
hideCircle()
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(this)
|
||||
hideCircle()
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
adapter.updateConfigList()
|
||||
mainViewModel.reloadServerList()
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
@@ -171,9 +195,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getOptionIntent() = Intent().putExtra("position", -1)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value == true)
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.import_qrcode -> {
|
||||
importQRcode(REQUEST_SCAN)
|
||||
@@ -184,18 +205,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
R.id.import_manually_vmess -> {
|
||||
startActivity(getOptionIntent().setClass(this, ServerActivity::class.java))
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_manually_ss -> {
|
||||
startActivity(getOptionIntent().setClass(this, Server3Activity::class.java))
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_manually_socks -> {
|
||||
startActivity(getOptionIntent().setClass(this, Server4Activity::class.java))
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_config_custom_clipboard -> {
|
||||
@@ -226,8 +247,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.export_all -> {
|
||||
if (AngConfigManager.shareAll2Clipboard() == 0) {
|
||||
//remove toast, otherwise it will block previous warning message
|
||||
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
@@ -288,10 +309,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
fun importBatchConfig(server: String?, subid: String = "") {
|
||||
val count = AngConfigManager.importBatchConfig(server, subid)
|
||||
var count = AngConfigManager.importBatchConfig(server, subid)
|
||||
if (count <= 0) {
|
||||
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
|
||||
}
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
adapter.updateConfigList()
|
||||
mainViewModel.reloadServerList()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
@@ -375,18 +399,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
: Boolean {
|
||||
try {
|
||||
toast(R.string.title_sub_update)
|
||||
val subItem = AngConfigManager.configs.subItem
|
||||
for (k in 0 until subItem.count()) {
|
||||
if (TextUtils.isEmpty(subItem[k].id)
|
||||
|| TextUtils.isEmpty(subItem[k].remarks)
|
||||
|| TextUtils.isEmpty(subItem[k].url)
|
||||
MmkvManager.decodeSubscriptions().forEach {
|
||||
if (TextUtils.isEmpty(it.first)
|
||||
|| TextUtils.isEmpty(it.second.remarks)
|
||||
|| TextUtils.isEmpty(it.second.url)
|
||||
) {
|
||||
continue
|
||||
return@forEach
|
||||
}
|
||||
val id = subItem[k].id
|
||||
val url = subItem[k].url
|
||||
val url = it.second.url
|
||||
if (!Utils.isValidUrl(url)) {
|
||||
continue
|
||||
return@forEach
|
||||
}
|
||||
Log.d("Main", url)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
@@ -397,7 +419,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
importBatchConfig(Utils.decode(configText), id)
|
||||
importBatchConfig(Utils.decode(configText), it.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,9 +456,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
contentResolver.openInputStream(uri).use {
|
||||
val configText = it?.bufferedReader()?.readText()
|
||||
importCustomizeConfig(configText)
|
||||
contentResolver.openInputStream(uri).use { input ->
|
||||
importCustomizeConfig(input?.bufferedReader()?.readText())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -450,19 +471,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
* import customize config
|
||||
*/
|
||||
fun importCustomizeConfig(server: String?) {
|
||||
if (server == null) {
|
||||
return
|
||||
}
|
||||
if (!V2rayConfigUtil.isValidConfig(server)) {
|
||||
toast(R.string.toast_config_file_invalid)
|
||||
return
|
||||
}
|
||||
val resId = AngConfigManager.importCustomizeConfig(server)
|
||||
if (resId > 0) {
|
||||
toast(resId)
|
||||
} else {
|
||||
try {
|
||||
if (server == null || TextUtils.isEmpty(server)) {
|
||||
toast(R.string.toast_none_data)
|
||||
return
|
||||
}
|
||||
mainViewModel.appendCustomConfigServer(server)
|
||||
toast(R.string.toast_success)
|
||||
adapter.updateConfigList()
|
||||
adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||
} catch (e: Exception) {
|
||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||
e.printStackTrace()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,25 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
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.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import kotlinx.android.synthetic.main.item_qrcode.view.*
|
||||
import kotlinx.android.synthetic.main.item_recycler_main.view.*
|
||||
import rx.Observable
|
||||
@@ -32,81 +35,70 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
private var mActivity: MainActivity = activity
|
||||
private lateinit var configs: AngConfig
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val share_method: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_method)
|
||||
}
|
||||
var isRunning = false
|
||||
|
||||
var changeable: Boolean = true
|
||||
set(value) {
|
||||
if (field == value)
|
||||
return
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
init {
|
||||
updateConfigList()
|
||||
}
|
||||
|
||||
override fun getItemCount() = configs.vmess.count() + 1
|
||||
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val configType = EConfigType.fromInt(configs.vmess[position].configType)
|
||||
val remarks = configs.vmess[position].remarks
|
||||
val subid = configs.vmess[position].subid
|
||||
val address = configs.vmess[position].address
|
||||
val port = configs.vmess[position].port
|
||||
val test_result = configs.vmess[position].testResult
|
||||
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||
val outbound = config.getProxyOutbound()
|
||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.radio.isChecked = (position == configs.index)
|
||||
holder.name.text = config.remarks
|
||||
holder.radio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
holder.test_result.text = test_result
|
||||
|
||||
if (TextUtils.isEmpty(subid)) {
|
||||
holder.subid.text = ""
|
||||
holder.test_result.text = aff?.getTestDelayString() ?: ""
|
||||
if (aff?.testDelayMillis?:0L < 0L) {
|
||||
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
|
||||
} else {
|
||||
holder.subid.text = "S"
|
||||
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||
}
|
||||
holder.subscription.text = ""
|
||||
val json = subStorage?.decodeString(config.subscriptionId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
holder.subscription.text = sub.remarks
|
||||
}
|
||||
|
||||
var shareOptions = share_method.asList()
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
holder.type.text = mActivity.getString(R.string.server_customize_config)
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
|
||||
if (serverOutbound == null) {
|
||||
holder.statistics.text = ""
|
||||
} else {
|
||||
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
|
||||
}
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
holder.type.text = config.configType.name
|
||||
} else {
|
||||
holder.type.text = configType?.name?.toLowerCase()
|
||||
holder.statistics.text = "$address : $port"
|
||||
holder.type.text = config.configType.name.toLowerCase()
|
||||
}
|
||||
holder.statistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
|
||||
|
||||
holder.layout_share.setOnClickListener {
|
||||
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(position)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(guid)
|
||||
} else {
|
||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
||||
val iv = LayoutInflater.from(mActivity).inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
|
||||
AlertDialog.Builder(mActivity).setView(iv).show()
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
if (AngConfigManager.share2Clipboard(position) == 0) {
|
||||
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
2 -> shareFullContent(position)
|
||||
2 -> shareFullContent(guid)
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -116,43 +108,39 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
val intent = Intent().putExtra("position", position)
|
||||
.putExtra("isRunning", !changeable)
|
||||
if (configType == EConfigType.VMESS) {
|
||||
val intent = Intent().putExtra("guid", guid)
|
||||
.putExtra("isRunning", isRunning)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||
} else {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||
} else if (configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, Server2Activity::class.java))
|
||||
} else if (configType == EConfigType.SHADOWSOCKS) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, Server3Activity::class.java))
|
||||
} else if (configType == EConfigType.SOCKS) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, Server4Activity::class.java))
|
||||
}
|
||||
}
|
||||
holder.layout_remove.setOnClickListener {
|
||||
if (configs.index != position) {
|
||||
if (AngConfigManager.removeServer(position) == 0) {
|
||||
notifyItemRemoved(position)
|
||||
updateSelectedItem(position)
|
||||
}
|
||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
|
||||
}
|
||||
}
|
||||
|
||||
holder.infoContainer.setOnClickListener {
|
||||
if (changeable) {
|
||||
AngConfigManager.setActiveServer(position)
|
||||
} else {
|
||||
mActivity.showCircle()
|
||||
Utils.stopVService(mActivity)
|
||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
mActivity.showCircle()
|
||||
if (!Utils.startVService(mActivity, position)) {
|
||||
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
if (guid != selected) {
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
|
||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
|
||||
if (isRunning) {
|
||||
mActivity.showCircle()
|
||||
Utils.stopVService(mActivity)
|
||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
V2RayServiceManager.startV2Ray(mActivity)
|
||||
mActivity.hideCircle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
if (holder is FooterViewHolder) {
|
||||
@@ -161,14 +149,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.layout_edit.visibility = View.INVISIBLE
|
||||
} else {
|
||||
holder.layout_edit.setOnClickListener {
|
||||
Utils.openUri(mActivity, AppConfig.promotionUrl)
|
||||
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFullContent(position: Int) {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
|
||||
private fun shareFullContent(guid: String) {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
@@ -176,42 +164,28 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
when (viewType) {
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_ITEM ->
|
||||
return MainViewHolder(LayoutInflater.from(parent.context)
|
||||
MainViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_recycler_main, parent, false))
|
||||
else ->
|
||||
return FooterViewHolder(LayoutInflater.from(parent.context)
|
||||
FooterViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_recycler_footer, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateConfigList() {
|
||||
configs = AngConfigManager.configs
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// fun updateSelectedItem() {
|
||||
// updateSelectedItem(configs.index)
|
||||
// }
|
||||
|
||||
fun updateSelectedItem(pos: Int) {
|
||||
//notifyItemChanged(pos)
|
||||
notifyItemRangeChanged(pos, itemCount - pos)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (position == configs.vmess.count()) {
|
||||
return VIEW_TYPE_FOOTER
|
||||
return if (position == mActivity.mainViewModel.serverList.size) {
|
||||
VIEW_TYPE_FOOTER
|
||||
} else {
|
||||
return VIEW_TYPE_ITEM
|
||||
VIEW_TYPE_ITEM
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
|
||||
val subid = itemView.tv_subid
|
||||
val subscription = itemView.tv_subscription!!
|
||||
val radio = itemView.btn_radio!!
|
||||
val name = itemView.tv_name!!
|
||||
val test_result = itemView.tv_test_result!!
|
||||
@@ -244,28 +218,26 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
if (configs.index != position) {
|
||||
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
// mActivity.alert(R.string.del_config_comfirm) {
|
||||
// positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(position) == 0) {
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
notifyItemRemoved(position)
|
||||
// }
|
||||
// show()
|
||||
// }
|
||||
}
|
||||
updateSelectedItem(position)
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
AngConfigManager.swapServer(fromPosition, toPosition)
|
||||
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
//notifyDataSetChanged()
|
||||
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
|
||||
//notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
AngConfigManager.storeConfigFile()
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import android.support.v7.widget.DividerItemDecoration
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
@@ -15,7 +16,6 @@ import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
import kotlinx.android.synthetic.main.activity_bypass_list.*
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@@ -35,13 +35,10 @@ import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
class PerAppProxyActivity : BaseActivity() {
|
||||
companion object {
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
}
|
||||
|
||||
private var adapter: PerAppProxyAdapter? = null
|
||||
private var appsAll: List<AppInfo>? = null
|
||||
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -52,7 +49,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||
recycler_view.addItemDecoration(dividerItemDecoration)
|
||||
|
||||
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
|
||||
val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null)
|
||||
|
||||
AppManagerUtil.rxLoadNetworkAppList(this)
|
||||
.subscribeOn(Schedulers.io())
|
||||
@@ -141,15 +138,15 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
|
||||
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
|
||||
switch_per_app_proxy.setOnCheckedChangeListener { _, isChecked ->
|
||||
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
|
||||
}
|
||||
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
|
||||
switch_per_app_proxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
|
||||
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
|
||||
switch_bypass_apps.setOnCheckedChangeListener { _, isChecked ->
|
||||
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
|
||||
}
|
||||
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
|
||||
switch_bypass_apps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
|
||||
|
||||
et_search.setOnEditorActionListener { v, actionId, event ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
@@ -183,7 +180,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
adapter?.let {
|
||||
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
|
||||
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import android.view.*
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
||||
import android.view.MenuInflater
|
||||
@@ -26,6 +26,8 @@ class RoutingSettingsFragment : Fragment() {
|
||||
private const val REQUEST_SCAN_APPEND = 12
|
||||
}
|
||||
|
||||
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
@@ -40,10 +42,10 @@ class RoutingSettingsFragment : Fragment() {
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "")
|
||||
val content = defaultSharedPreferences.getString(arguments!!.getString(routing_arg), "")
|
||||
et_routing_content.text = Utils.getEditable(content!!)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
@@ -57,7 +59,7 @@ class RoutingSettingsFragment : Fragment() {
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.save_routing -> {
|
||||
val content = et_routing_content.text.toString()
|
||||
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content)
|
||||
defaultSharedPreferences.edit().putString(arguments!!.getString(routing_arg), content).apply()
|
||||
activity?.toast(R.string.toast_success)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
|
||||
private var mScannerView: ZXingScannerView? = null
|
||||
|
||||
public override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
|
||||
|
||||
mScannerView?.setAutoFocus(true)
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server2.*
|
||||
import java.lang.Exception
|
||||
|
||||
class Server2Activity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
}
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的服务器
|
||||
private var edit_guid: String = ""
|
||||
private var isRunning: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server2)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
edit_guid = configs.vmess[edit_index].guid
|
||||
bindingServer(configs.vmess[edit_index])
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
||||
tv_content.text = Editable.Factory.getInstance().newEditable(defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + edit_guid, ""))
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val vmess = configs.vmess[edit_index]
|
||||
|
||||
vmess.remarks = et_remarks.text.toString()
|
||||
|
||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toast(R.string.toast_malformed_josn)
|
||||
return false
|
||||
}
|
||||
|
||||
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
|
||||
//update config
|
||||
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
|
||||
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
del_config = menu?.findItem(R.id.del_config)
|
||||
save_config = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
if (isRunning) {
|
||||
if (edit_index == configs.index) {
|
||||
del_config?.isVisible = false
|
||||
save_config?.isVisible = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server3.*
|
||||
|
||||
class Server3Activity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
}
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的服务器
|
||||
private var edit_guid: String = ""
|
||||
private var isRunning: Boolean = false
|
||||
private val securitys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.ss_securitys)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server3)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
edit_guid = configs.vmess[edit_index].guid
|
||||
bindingServer(configs.vmess[edit_index])
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
||||
|
||||
et_address.text = Utils.getEditable(vmess.address)
|
||||
et_port.text = Utils.getEditable(vmess.port.toString())
|
||||
et_id.text = Utils.getEditable(vmess.id)
|
||||
val security = Utils.arrayFind(securitys, vmess.security)
|
||||
if (security >= 0) {
|
||||
sp_security.setSelection(security)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
et_address.text = null
|
||||
et_port.text = Utils.getEditable("10086")
|
||||
et_id.text = null
|
||||
sp_security.setSelection(0)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val vmess: AngConfig.VmessBean
|
||||
if (edit_index >= 0) {
|
||||
vmess = configs.vmess[edit_index]
|
||||
} else {
|
||||
vmess = AngConfig.VmessBean()
|
||||
}
|
||||
|
||||
vmess.guid = edit_guid
|
||||
vmess.remarks = et_remarks.text.toString()
|
||||
vmess.address = et_address.text.toString()
|
||||
vmess.port = Utils.parseInt(et_port.text.toString())
|
||||
vmess.id = et_id.text.toString()
|
||||
vmess.security = securitys[sp_security.selectedItemPosition]
|
||||
|
||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.address)) {
|
||||
toast(R.string.server_lab_address3)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
|
||||
toast(R.string.server_lab_port3)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.id)) {
|
||||
toast(R.string.server_lab_id3)
|
||||
return false
|
||||
}
|
||||
|
||||
if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) {
|
||||
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
del_config = menu?.findItem(R.id.del_config)
|
||||
save_config = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
if (isRunning) {
|
||||
if (edit_index == configs.index) {
|
||||
del_config?.isVisible = false
|
||||
save_config?.isVisible = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server4.*
|
||||
|
||||
class Server4Activity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
}
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的服务器
|
||||
private var edit_guid: String = ""
|
||||
private var isRunning: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server4)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
edit_guid = configs.vmess[edit_index].guid
|
||||
bindingServer(configs.vmess[edit_index])
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
||||
|
||||
et_address.text = Utils.getEditable(vmess.address)
|
||||
et_port.text = Utils.getEditable(vmess.port.toString())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
et_address.text = null
|
||||
et_port.text = Utils.getEditable("10086")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val vmess: AngConfig.VmessBean
|
||||
if (edit_index >= 0) {
|
||||
vmess = configs.vmess[edit_index]
|
||||
} else {
|
||||
vmess = AngConfig.VmessBean()
|
||||
}
|
||||
|
||||
vmess.guid = edit_guid
|
||||
vmess.remarks = et_remarks.text.toString()
|
||||
vmess.address = et_address.text.toString()
|
||||
vmess.port = Utils.parseInt(et_port.text.toString())
|
||||
|
||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.address)) {
|
||||
toast(R.string.server_lab_address3)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
|
||||
toast(R.string.server_lab_port3)
|
||||
return false
|
||||
}
|
||||
|
||||
if (AngConfigManager.addSocksServer(vmess, edit_index) == 0) {
|
||||
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
del_config = menu?.findItem(R.id.del_config)
|
||||
save_config = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
if (isRunning) {
|
||||
if (edit_index == configs.index) {
|
||||
del_config?.isVisible = false
|
||||
save_config?.isVisible = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -5,50 +5,82 @@ import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server.*
|
||||
import kotlinx.android.synthetic.main.activity_server_socks.*
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.*
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_address
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_id
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_path
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_port
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_remarks
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_request_host
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_allow_insecure
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_network
|
||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
|
||||
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)
|
||||
}
|
||||
private val createConfigType by lazy {
|
||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
||||
}
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的服务器
|
||||
private var edit_guid: String = ""
|
||||
private var isRunning: Boolean = false
|
||||
private val securitys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.securitys)
|
||||
}
|
||||
private val shadowsocksSecuritys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.ss_securitys)
|
||||
}
|
||||
private val flows: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.flows)
|
||||
}
|
||||
private val networks: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.networks)
|
||||
}
|
||||
private val headertypes: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.headertypes)
|
||||
}
|
||||
private val streamsecuritys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.streamsecuritys)
|
||||
private val streamSecuritys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.streamsecurityxs)
|
||||
}
|
||||
private val allowinsecures: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.allowinsecures)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
edit_guid = configs.vmess[edit_index].guid
|
||||
bindingServer(configs.vmess[edit_index])
|
||||
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||
when(config?.configType ?: createConfigType) {
|
||||
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
|
||||
EConfigType.CUSTOM -> return
|
||||
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||
}
|
||||
if (config != null) {
|
||||
bindingServer(config)
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
@@ -58,33 +90,49 @@ class ServerActivity : BaseActivity() {
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
||||
private fun bindingServer(config: ServerConfig): Boolean {
|
||||
val outbound = config.getProxyOutbound() ?: return false
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||
|
||||
et_address.text = Utils.getEditable(vmess.address)
|
||||
et_port.text = Utils.getEditable(vmess.port.toString())
|
||||
et_id.text = Utils.getEditable(vmess.id)
|
||||
et_alterId.text = Utils.getEditable(vmess.alterId.toString())
|
||||
|
||||
val security = Utils.arrayFind(securitys, vmess.security)
|
||||
et_remarks.text = Utils.getEditable(config.remarks)
|
||||
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
|
||||
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
|
||||
et_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) {
|
||||
et_security.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
et_security.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
|
||||
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
|
||||
if (flow >= 0) {
|
||||
//sp_flow.setSelection(flow)
|
||||
}
|
||||
}
|
||||
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
|
||||
if (security >= 0) {
|
||||
sp_security.setSelection(security)
|
||||
}
|
||||
val network = Utils.arrayFind(networks, vmess.network)
|
||||
if (network >= 0) {
|
||||
sp_network.setSelection(network)
|
||||
sp_security?.setSelection(security)
|
||||
}
|
||||
|
||||
val headerType = Utils.arrayFind(headertypes, vmess.headerType)
|
||||
if (headerType >= 0) {
|
||||
sp_header_type.setSelection(headerType)
|
||||
}
|
||||
et_request_host.text = Utils.getEditable(vmess.requestHost)
|
||||
et_path.text = Utils.getEditable(vmess.path)
|
||||
|
||||
val streamSecurity = Utils.arrayFind(streamsecuritys, vmess.streamSecurity)
|
||||
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
|
||||
if (streamSecurity >= 0) {
|
||||
sp_stream_security.setSelection(streamSecurity)
|
||||
sp_stream_security?.setSelection(streamSecurity)
|
||||
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
||||
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||
if (allowinsecure >= 0) {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
et_request_host.text = Utils.getEditable(tlsSetting.serverName)
|
||||
}
|
||||
}
|
||||
val network = Utils.arrayFind(networks, streamSetting.network)
|
||||
if (network >= 0) {
|
||||
sp_network?.setSelection(network)
|
||||
}
|
||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||
sp_header_type.setSelection(Utils.arrayFind(headertypes, transportDetails[0]))
|
||||
et_request_host.text = Utils.getEditable(transportDetails[1])
|
||||
et_path.text = Utils.getEditable(transportDetails[2])
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -92,113 +140,163 @@ class ServerActivity : BaseActivity() {
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
private fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
et_address.text = null
|
||||
et_port.text = Utils.getEditable("10086")
|
||||
et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
|
||||
et_id.text = null
|
||||
et_alterId.text = Utils.getEditable("64")
|
||||
sp_security.setSelection(0)
|
||||
sp_network.setSelection(0)
|
||||
et_alterId?.text = Utils.getEditable("0")
|
||||
sp_security?.setSelection(0)
|
||||
sp_network?.setSelection(0)
|
||||
|
||||
sp_header_type.setSelection(0)
|
||||
et_request_host.text = null
|
||||
et_path.text = null
|
||||
sp_stream_security.setSelection(0)
|
||||
sp_header_type?.setSelection(0)
|
||||
et_request_host?.text = null
|
||||
et_path?.text = null
|
||||
sp_stream_security?.setSelection(0)
|
||||
sp_allow_insecure?.setSelection(0)
|
||||
|
||||
//et_security.text = null
|
||||
//sp_flow?.setSelection(0)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val vmess: AngConfig.VmessBean
|
||||
if (edit_index >= 0) {
|
||||
vmess = configs.vmess[edit_index]
|
||||
} else {
|
||||
vmess = AngConfig.VmessBean()
|
||||
}
|
||||
|
||||
vmess.guid = edit_guid
|
||||
vmess.remarks = et_remarks.text.toString()
|
||||
vmess.address = et_address.text.toString()
|
||||
vmess.port = Utils.parseInt(et_port.text.toString())
|
||||
vmess.id = et_id.text.toString()
|
||||
vmess.alterId = Utils.parseInt(et_alterId.text.toString())
|
||||
vmess.security = securitys[sp_security.selectedItemPosition]
|
||||
vmess.network = networks[sp_network.selectedItemPosition]
|
||||
|
||||
vmess.headerType = headertypes[sp_header_type.selectedItemPosition]
|
||||
vmess.requestHost = et_request_host.text.toString()
|
||||
vmess.path = et_path.text.toString()
|
||||
vmess.streamSecurity = streamsecuritys[sp_stream_security.selectedItemPosition]
|
||||
|
||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
||||
private fun saveServer(): Boolean {
|
||||
if (TextUtils.isEmpty(et_remarks.text.toString())) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.address)) {
|
||||
if (TextUtils.isEmpty(et_address.text.toString())) {
|
||||
toast(R.string.server_lab_address)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
|
||||
val port = Utils.parseInt(et_port.text.toString())
|
||||
if (port <= 0) {
|
||||
toast(R.string.server_lab_port)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.id)) {
|
||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
|
||||
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
|
||||
toast(R.string.server_lab_id)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) {
|
||||
toast(R.string.server_lab_alterid)
|
||||
return false
|
||||
et_alterId?.let {
|
||||
val alterId = Utils.parseInt(et_alterId.text.toString())
|
||||
if (alterId < 0) {
|
||||
toast(R.string.server_lab_alterid)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (AngConfigManager.addServer(vmess, edit_index) == 0) {
|
||||
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
config.remarks = et_remarks.text.toString().trim()
|
||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
saveVnext(vnext, port, config)
|
||||
}
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
saveServers(server, port, config)
|
||||
}
|
||||
config.outboundBean?.streamSettings?.let {
|
||||
saveStreamSettings(it, config)
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) {
|
||||
vnext.address = et_address.text.toString().trim()
|
||||
vnext.port = port
|
||||
vnext.users[0].id = et_id.text.toString().trim()
|
||||
if (config.configType == EConfigType.VMESS) {
|
||||
vnext.users[0].alterId = Utils.parseInt(et_alterId.text.toString())
|
||||
vnext.users[0].security = securitys[sp_security.selectedItemPosition]
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
vnext.users[0].encryption = et_security.text.toString().trim()
|
||||
if (streamSecuritys[sp_stream_security.selectedItemPosition] == XTLS) {
|
||||
// vnext.users[0].flow = if (flows[sp_flow.selectedItemPosition].isBlank()) V2rayConfig.DEFAULT_FLOW
|
||||
// else flows[sp_flow.selectedItemPosition]
|
||||
} else {
|
||||
vnext.users[0].flow = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) {
|
||||
server.address = et_address.text.toString().trim()
|
||||
server.port = port
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||
server.password = et_id.text.toString().trim()
|
||||
server.method = shadowsocksSecuritys[sp_security.selectedItemPosition]
|
||||
} else if (config.configType == EConfigType.SOCKS) {
|
||||
if (TextUtils.isEmpty(et_security.text) && TextUtils.isEmpty(et_id.text)) {
|
||||
server.users = null
|
||||
} else {
|
||||
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
socksUsersBean.user = et_security.text.toString().trim()
|
||||
socksUsersBean.pass = et_id.text.toString().trim()
|
||||
server.users = listOf(socksUsersBean)
|
||||
}
|
||||
} else if (config.configType == EConfigType.TROJAN) {
|
||||
server.password = et_id.text.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, config: ServerConfig) {
|
||||
val requestHost = et_request_host?.text.toString().trim()
|
||||
val path = et_path?.text.toString().trim()
|
||||
var sni = streamSetting.populateTransportSettings(
|
||||
if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK,
|
||||
if (sp_header_type != null) headertypes[sp_header_type.selectedItemPosition] else "",
|
||||
requestHost,
|
||||
path,
|
||||
path,
|
||||
requestHost,
|
||||
path
|
||||
)
|
||||
val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) {
|
||||
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
||||
} else {
|
||||
allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean()
|
||||
}
|
||||
val defaultTls = if (config.configType == EConfigType.TROJAN) V2rayConfig.TLS else ""
|
||||
streamSetting.populateTlsSettings(
|
||||
if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else defaultTls,
|
||||
allowInsecure,
|
||||
sni
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
MmkvManager.removeServer(editGuid)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
del_config = menu?.findItem(R.id.del_config)
|
||||
save_config = menu?.findItem(R.id.save_config)
|
||||
val delButton = menu?.findItem(R.id.del_config)
|
||||
val saveButton = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
if (isRunning) {
|
||||
if (edit_index == configs.index) {
|
||||
del_config?.isVisible = false
|
||||
save_config?.isVisible = false
|
||||
}
|
||||
delButton?.isVisible = false
|
||||
saveButton?.isVisible = false
|
||||
}
|
||||
} else {
|
||||
del_config?.isVisible = false
|
||||
delButton?.isVisible = false
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server_custom_config.*
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
|
||||
class ServerCustomConfigActivity : BaseActivity() {
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, 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)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server_custom_config)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||
if (config != null) {
|
||||
bindingServer(config)
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
private fun bindingServer(config: ServerConfig): Boolean {
|
||||
et_remarks.text = Utils.getEditable(config.remarks)
|
||||
tv_content.text = Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty())
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
private fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
private fun saveServer(): Boolean {
|
||||
if (TextUtils.isEmpty(et_remarks.text.toString())) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
|
||||
val v2rayConfig = try {
|
||||
Gson().fromJson(tv_content.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()
|
||||
return false
|
||||
}
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.remarks = et_remarks.text.toString().trim()
|
||||
config.fullConfig = v2rayConfig
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
MmkvManager.removeServer(editGuid)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
val delButton = menu?.findItem(R.id.del_config)
|
||||
val saveButton = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (editGuid.isNotEmpty()) {
|
||||
if (isRunning) {
|
||||
delButton?.isVisible = false
|
||||
saveButton?.isVisible = false
|
||||
}
|
||||
} else {
|
||||
delButton?.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)
|
||||
}
|
||||
}
|
||||
@@ -8,36 +8,11 @@ import android.view.View
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
companion object {
|
||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
||||
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||
|
||||
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
// const val PREF_HTTP_PORT = "pref_http_port"
|
||||
|
||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
// const val PREF_DONATE = "pref_donate"
|
||||
// const val PREF_LICENSES = "pref_licenses"
|
||||
// const val PREF_FEEDBACK = "pref_feedback"
|
||||
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||
}
|
||||
|
||||
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
|
||||
|
||||
@@ -53,17 +28,17 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
private val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
private val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
private val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
private val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
private val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
|
||||
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
|
||||
|
||||
private val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
private val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
private val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
private val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
|
||||
private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
|
||||
|
||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||
|
||||
@@ -71,7 +46,7 @@ class SettingsActivity : BaseActivity() {
|
||||
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
|
||||
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
|
||||
|
||||
private val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
|
||||
private val routingCustom: Preference by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) }
|
||||
// val donate: Preference by lazy { findPreference(PREF_DONATE) }
|
||||
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||
@@ -81,7 +56,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
private fun restartProxy() {
|
||||
Utils.stopVService(requireContext())
|
||||
Utils.startVService(requireContext(), AngConfigManager.configs.index)
|
||||
V2RayServiceManager.startV2Ray(requireContext())
|
||||
}
|
||||
|
||||
private fun isRunning(): Boolean {
|
||||
@@ -214,8 +189,8 @@ class SettingsActivity : BaseActivity() {
|
||||
super.onStart()
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
updatePerAppProxy(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
|
||||
remoteDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
||||
|
||||
if (remoteDns.summary == "") {
|
||||
remoteDns.summary = AppConfig.DNS_AGENT
|
||||
@@ -233,7 +208,7 @@ class SettingsActivity : BaseActivity() {
|
||||
if (mode == "VPN") {
|
||||
perAppProxy.isEnabled = true
|
||||
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(PREF_PER_APP_PROXY, false)
|
||||
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
} else {
|
||||
perAppProxy.isEnabled = false
|
||||
perAppProxy.isChecked = false
|
||||
|
||||
@@ -5,10 +5,12 @@ import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_sub_edit.*
|
||||
|
||||
@@ -17,20 +19,17 @@ class SubEditActivity : BaseActivity() {
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_sub_edit)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
bindingServer(configs.subItem[edit_index])
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
@@ -40,7 +39,7 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean {
|
||||
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
||||
et_remarks.text = Utils.getEditable(subItem.remarks)
|
||||
et_url.text = Utils.getEditable(subItem.url)
|
||||
|
||||
@@ -50,7 +49,7 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
private fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
et_url.text = null
|
||||
|
||||
@@ -60,12 +59,15 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val subItem: AngConfig.SubItemBean
|
||||
if (edit_index >= 0) {
|
||||
subItem = configs.subItem[edit_index]
|
||||
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 {
|
||||
subItem = AngConfig.SubItemBean()
|
||||
subId = Utils.getUuid()
|
||||
subItem = SubscriptionItem()
|
||||
}
|
||||
|
||||
subItem.remarks = et_remarks.text.toString()
|
||||
@@ -80,32 +82,23 @@ class SubEditActivity : BaseActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
}
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editSubId.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeSubItem(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
MmkvManager.removeSubscription(editSubId)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -115,8 +108,7 @@ class SubEditActivity : BaseActivity() {
|
||||
del_config = menu?.findItem(R.id.del_config)
|
||||
save_config = menu?.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
} else {
|
||||
if (editSubId.isEmpty()) {
|
||||
del_config?.isVisible = false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import kotlinx.android.synthetic.main.activity_sub_setting.*
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
class SubSettingActivity : BaseActivity() {
|
||||
|
||||
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
|
||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -27,7 +30,8 @@ class SubSettingActivity : BaseActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
adapter.updateConfigList()
|
||||
subscriptions = MmkvManager.decodeSubscriptions()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
@@ -40,14 +44,9 @@ class SubSettingActivity : BaseActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.add_config -> {
|
||||
startActivity(Intent(this, SubEditActivity::class.java)
|
||||
.putExtra("position", -1)
|
||||
)
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent(this, SubEditActivity::class.java))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,36 +7,27 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
|
||||
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
|
||||
|
||||
private var mActivity: SubSettingActivity = activity
|
||||
private lateinit var configs: AngConfig
|
||||
|
||||
init {
|
||||
updateConfigList()
|
||||
}
|
||||
|
||||
override fun getItemCount() = configs.subItem.count()
|
||||
override fun getItemCount() = mActivity.subscriptions.size
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val remarks = configs.subItem[position].remarks
|
||||
val url = configs.subItem[position].url
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.url.text = url
|
||||
val subId = mActivity.subscriptions[position].first
|
||||
val subItem = mActivity.subscriptions[position].second
|
||||
holder.name.text = subItem.remarks
|
||||
holder.url.text = subItem.url
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
|
||||
.putExtra("position", position)
|
||||
.putExtra("subId", subId)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +36,6 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
.inflate(R.layout.item_recycler_sub_setting, parent, false))
|
||||
}
|
||||
|
||||
fun updateConfigList() {
|
||||
configs = AngConfigManager.configs
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// fun updateSelectedItem() {
|
||||
// notifyItemChanged(configs.index)
|
||||
// }
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
|
||||
|
||||
@@ -7,21 +7,23 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import java.util.ArrayList
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.zxing.WriterException
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import kotlinx.android.synthetic.main.activity_tasker.*
|
||||
|
||||
|
||||
class TaskerActivity : BaseActivity() {
|
||||
private var listview: ListView? = null
|
||||
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(R.layout.activity_tasker)
|
||||
@@ -30,9 +32,11 @@ class TaskerActivity : BaseActivity() {
|
||||
lstData.add("Default")
|
||||
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
|
||||
|
||||
AngConfigManager.configs.vmess.forEach {
|
||||
lstData.add(it.remarks)
|
||||
lstGuid.add(it.guid)
|
||||
serverStorage?.allKeys()?.forEach { key ->
|
||||
MmkvManager.decodeServerConfig(key)?.let { config ->
|
||||
lstData.add(config.remarks)
|
||||
lstGuid.add(key)
|
||||
}
|
||||
}
|
||||
val adapter = ArrayAdapter(this,
|
||||
android.R.layout.simple_list_item_single_choice, lstData)
|
||||
@@ -75,12 +79,10 @@ class TaskerActivity : BaseActivity() {
|
||||
val intent = Intent()
|
||||
|
||||
val remarks = lstData[position]
|
||||
var blurb = ""
|
||||
|
||||
if (switch_start_service.isChecked) {
|
||||
blurb = "Start $remarks"
|
||||
val blurb = if (switch_start_service.isChecked) {
|
||||
"Start $remarks"
|
||||
} else {
|
||||
blurb = "Stop $remarks"
|
||||
"Stop $remarks"
|
||||
}
|
||||
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||
@@ -108,4 +110,3 @@ class TaskerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ object AppManagerUtil {
|
||||
return apps
|
||||
}
|
||||
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
|
||||
it.onNext(loadNetworkAppList(ctx))
|
||||
}
|
||||
|
||||
|
||||
147
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
147
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
@@ -0,0 +1,147 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
|
||||
object MmkvManager {
|
||||
const val ID_MAIN = "MAIN"
|
||||
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
||||
const val ID_SERVER_AFF = "SERVER_AFF"
|
||||
const val ID_SUB = "SUB"
|
||||
const val ID_SETTING = "SETTING"
|
||||
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
fun decodeServerList(): MutableList<String> {
|
||||
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||
return if (json.isNullOrBlank()) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
Gson().fromJson(json, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeServerConfig(guid: String): ServerConfig? {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = serverStorage?.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerConfig::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||
val key = if (guid.isBlank()) {
|
||||
Utils.getUuid()
|
||||
} else {
|
||||
guid
|
||||
}
|
||||
serverStorage?.encode(key, Gson().toJson(config))
|
||||
val serverList= decodeServerList()
|
||||
if (!serverList.contains(key)) {
|
||||
serverList.add(key)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
fun removeServer(guid: String) {
|
||||
if (guid.isBlank()) {
|
||||
return
|
||||
}
|
||||
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
|
||||
mainStorage?.remove(KEY_SELECTED_SERVER)
|
||||
}
|
||||
val serverList= decodeServerList()
|
||||
serverList.remove(guid)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
serverStorage?.remove(guid)
|
||||
serverAffStorage?.remove(guid)
|
||||
}
|
||||
|
||||
fun removeServerViaSubid(subid: String) {
|
||||
if (subid.isBlank()) {
|
||||
return
|
||||
}
|
||||
serverStorage?.allKeys()?.forEach { key ->
|
||||
decodeServerConfig(key)?.let { config ->
|
||||
if (config.subscriptionId == subid) {
|
||||
removeServer(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeServerAffiliationInfo(guid: String): ServerAffiliationInfo? {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = serverAffStorage?.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
|
||||
if (guid.isBlank()) {
|
||||
return
|
||||
}
|
||||
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
|
||||
aff.testDelayMillis = testResult
|
||||
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
||||
}
|
||||
|
||||
fun clearAllTestDelayResults() {
|
||||
serverAffStorage?.allKeys()?.forEach { key ->
|
||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||
aff.testDelayMillis = 0
|
||||
serverAffStorage?.encode(key, Gson().toJson(aff))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importUrlAsSubscription(url: String): Int {
|
||||
val subscriptions = decodeSubscriptions()
|
||||
subscriptions.forEach {
|
||||
if (it.second.url == url) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = "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)))
|
||||
}
|
||||
}
|
||||
subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||
return subscriptions
|
||||
}
|
||||
|
||||
fun removeSubscription(subid: String) {
|
||||
subStorage?.remove(subid)
|
||||
removeServerViaSubid(subid)
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,22 @@ import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.webkit.URLUtil
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.extension.responseLength
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import kotlinx.coroutines.isActive
|
||||
import me.dozen.dpreference.DPreference
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
object Utils {
|
||||
|
||||
val tcpTestingSockets = ArrayList<Socket?>()
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val tcpTestingSockets = ArrayList<Socket?>()
|
||||
|
||||
/**
|
||||
* convert string to editalbe for kotlin
|
||||
@@ -125,8 +123,8 @@ object Utils {
|
||||
/**
|
||||
* get remote dns servers from preference
|
||||
*/
|
||||
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
||||
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT)
|
||||
fun getRemoteDnsServers(): ArrayList<String> {
|
||||
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
|
||||
val ret = ArrayList<String>()
|
||||
if (!TextUtils.isEmpty(remoteDns)) {
|
||||
remoteDns
|
||||
@@ -146,8 +144,8 @@ object Utils {
|
||||
/**
|
||||
* get remote dns servers from preference
|
||||
*/
|
||||
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
||||
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
||||
fun getDomesticDnsServers(): ArrayList<String> {
|
||||
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||
val ret = ArrayList<String>()
|
||||
if (!TextUtils.isEmpty(domesticDns)) {
|
||||
domesticDns
|
||||
@@ -260,7 +258,7 @@ object Utils {
|
||||
*/
|
||||
fun isValidUrl(value: String?): Boolean {
|
||||
try {
|
||||
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
||||
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
||||
return true
|
||||
}
|
||||
} catch (e: WriterException) {
|
||||
@@ -271,8 +269,7 @@ object Utils {
|
||||
}
|
||||
|
||||
fun startVServiceFromToggle(context: Context): Boolean {
|
||||
val result = context.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
if (result.isBlank()) {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
context.toast(R.string.app_tile_first_use)
|
||||
return false
|
||||
}
|
||||
@@ -280,26 +277,6 @@ object Utils {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* startVService
|
||||
*/
|
||||
fun startVService(context: Context, guid: String): Boolean {
|
||||
val index = AngConfigManager.getIndexViaGuid(guid)
|
||||
context.v2RayApplication.curIndex=index
|
||||
return startVService(context, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* startVService
|
||||
*/
|
||||
fun startVService(context: Context, index: Int): Boolean {
|
||||
if (AngConfigManager.setActiveServer(index) < 0) {
|
||||
return false
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(context)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* stopVService
|
||||
*/
|
||||
@@ -399,8 +376,8 @@ object Utils {
|
||||
/**
|
||||
* readTextFromAssets
|
||||
*/
|
||||
fun readTextFromAssets(app: AngApplication, fileName: String): String {
|
||||
val content = app.assets.open(fileName).bufferedReader().use {
|
||||
fun readTextFromAssets(context: Context, fileName: String): String {
|
||||
val content = context.assets.open(fileName).bufferedReader().use {
|
||||
it.readText()
|
||||
}
|
||||
return content
|
||||
@@ -430,19 +407,18 @@ object Utils {
|
||||
/**
|
||||
* tcping
|
||||
*/
|
||||
suspend fun tcping(url: String, port: Int): String {
|
||||
suspend fun tcping(url: String, port: Int): Long {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (!coroutineContext.isActive) {
|
||||
break
|
||||
}
|
||||
if (one != -1L )
|
||||
if(time == -1L || one < time) {
|
||||
if (one != -1L && (time == -1L || one < time)) {
|
||||
time = one
|
||||
}
|
||||
}
|
||||
return time.toString() + "ms"
|
||||
return time
|
||||
}
|
||||
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
|
||||
@@ -4,166 +4,107 @@ import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.*
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
object V2rayConfigUtil {
|
||||
private val requestObj: JsonObject by lazy {
|
||||
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 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"}}""", JsonObject::class.java)
|
||||
}
|
||||
|
||||
// private val requestObj: JsonObject by lazy {
|
||||
// Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 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"}}""", JsonObject::class.java)
|
||||
// }
|
||||
// private val responseObj: JSONObject by lazy {
|
||||
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
|
||||
// }
|
||||
|
||||
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(app: AngApplication, vmess: VmessBean): Result {
|
||||
var result = Result(false, "")
|
||||
fun getV2rayConfig(context: Context, guid: String): Result {
|
||||
try {
|
||||
//检查设置
|
||||
// if (config.index < 0
|
||||
// || config.vmess.count() <= 0
|
||||
// || config.index > config.vmess.count() - 1
|
||||
// ) {
|
||||
// return result
|
||||
// }
|
||||
|
||||
if (vmess.configType == EConfigType.CUSTOM.value) {
|
||||
result = getV2rayConfigType2(app, vmess)
|
||||
} else {
|
||||
result = getV2rayConfigType1(app, vmess)
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val customConfig = config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
|
||||
Log.d("V2rayConfigUtilGoLog", customConfig)
|
||||
return Result(true, customConfig)
|
||||
}
|
||||
|
||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||
val result = getV2rayNonCustomConfig(context, outbound)
|
||||
Log.d("V2rayConfigUtilGoLog", result.content)
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun getCustomConfigServerOutbound(content: Context, guid: String): V2rayConfig.OutboundBean? {
|
||||
val jsonConfig = content.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
|
||||
if (TextUtils.isEmpty(jsonConfig)) {
|
||||
return null
|
||||
}
|
||||
val v2rayConfig: V2rayConfig? = try {
|
||||
Gson().fromJson(jsonConfig, V2rayConfig::class.java)
|
||||
} catch (e: JsonSyntaxException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
v2rayConfig?.outbounds?.forEach { outbound ->
|
||||
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
|
||||
outbound.protocol.equals(EConfigType.SOCKS.name, true)) {
|
||||
return outbound
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
|
||||
val result = Result(false, "")
|
||||
try {
|
||||
//取得默认配置
|
||||
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
|
||||
//转成Json
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
// if (v2rayConfig == null) {
|
||||
// return result
|
||||
// }
|
||||
|
||||
inbounds(vmess, v2rayConfig, app)
|
||||
|
||||
outbounds(vmess, v2rayConfig, app)
|
||||
|
||||
routing(vmess, v2rayConfig, app)
|
||||
|
||||
if (app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)) {
|
||||
customLocalDns(vmess, v2rayConfig, app)
|
||||
} else {
|
||||
customRemoteDns(vmess, v2rayConfig, app)
|
||||
}
|
||||
|
||||
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
|
||||
|
||||
result.status = true
|
||||
result.content = finalConfig
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
return Result(false, "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result {
|
||||
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
|
||||
val result = Result(false, "")
|
||||
try {
|
||||
val guid = vmess.guid
|
||||
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
|
||||
result.status = true
|
||||
result.content = jsonConfig
|
||||
parseDomainNameAndTag(app, jsonConfig)
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
//取得默认配置
|
||||
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"
|
||||
|
||||
inbounds(v2rayConfig)
|
||||
|
||||
v2rayConfig.outbounds[0] = outbound
|
||||
|
||||
routing(v2rayConfig)
|
||||
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||
customLocalDns(v2rayConfig)
|
||||
} else {
|
||||
customRemoteDns(v2rayConfig)
|
||||
}
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
||||
v2rayConfig.stats = null
|
||||
v2rayConfig.policy = null
|
||||
}
|
||||
result.status = true
|
||||
result.content = v2rayConfig.toPrettyPrinting()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
|
||||
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
|
||||
|
||||
v2rayConfig.inbounds.forEach { curInbound ->
|
||||
if (!app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
|
||||
if (!(settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) ?: false)) {
|
||||
//bind all inbounds to localhost if the user requests
|
||||
curInbound.listen = "127.0.0.1"
|
||||
}
|
||||
}
|
||||
v2rayConfig.inbounds[0].port = 10808
|
||||
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
||||
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
|
||||
v2rayConfig.inbounds[0].sniffing?.enabled = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED) ?: true
|
||||
|
||||
// if (socksPort > 0) {
|
||||
// v2rayConfig.inbounds[0].port = socksPort
|
||||
// }
|
||||
// if (lanconnPort > 0) {
|
||||
//v2rayConfig.inbounds[1].port = httpPort
|
||||
|
||||
// if (httpPort > 0) {
|
||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
||||
// httpCopy.port = lanconnPort
|
||||
// httpCopy.port = httpPort
|
||||
// httpCopy.protocol = "http"
|
||||
// v2rayConfig.inbounds.add(httpCopy)
|
||||
// }
|
||||
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
@@ -171,230 +112,26 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* vmess协议服务器配置
|
||||
*/
|
||||
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
try {
|
||||
val outbound = v2rayConfig.outbounds[0]
|
||||
|
||||
val configType = EConfigType.fromInt(vmess.configType)
|
||||
if (configType != null) {
|
||||
outbound.protocol = configType.name.toLowerCase()
|
||||
}
|
||||
when (configType) {
|
||||
EConfigType.VMESS -> {
|
||||
outbound.settings?.servers = null
|
||||
|
||||
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
|
||||
vnext?.address = vmess.address
|
||||
vnext?.port = vmess.port
|
||||
val user = vnext?.users?.get(0)
|
||||
user?.id = vmess.id
|
||||
user?.alterId = vmess.alterId
|
||||
user?.security = vmess.security
|
||||
user?.level = 8
|
||||
|
||||
//Mux
|
||||
val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false)
|
||||
outbound.mux?.enabled = muxEnabled
|
||||
|
||||
//远程服务器底层传输配置
|
||||
outbound.streamSettings = boundStreamSettings(vmess)
|
||||
}
|
||||
EConfigType.SHADOWSOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
server?.address = vmess.address
|
||||
server?.method = vmess.security
|
||||
server?.ota = false
|
||||
server?.password = vmess.id
|
||||
server?.port = vmess.port
|
||||
server?.level = 8
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
}
|
||||
EConfigType.SOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
server?.address = vmess.address
|
||||
server?.port = vmess.port
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
|
||||
String.format("[%s]:%s", vmess.address, vmess.port)
|
||||
} else {
|
||||
String.format("%s:%s", vmess.address, vmess.port)
|
||||
}
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
|
||||
val tags = LinkedHashSet<String>()
|
||||
v2rayConfig.outbounds.forEach {
|
||||
if (!TextUtils.isEmpty(it.tag)) {
|
||||
tags.add(it.tag)
|
||||
}
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程服务器底层传输配置
|
||||
*/
|
||||
private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean {
|
||||
val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null, null)
|
||||
try {
|
||||
//远程服务器底层传输配置
|
||||
streamSettings.network = vmess.network
|
||||
streamSettings.security = vmess.streamSecurity
|
||||
|
||||
//streamSettings
|
||||
when (streamSettings.network) {
|
||||
"kcp" -> {
|
||||
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
|
||||
kcpsettings.mtu = 1350
|
||||
kcpsettings.tti = 50
|
||||
kcpsettings.uplinkCapacity = 12
|
||||
kcpsettings.downlinkCapacity = 100
|
||||
kcpsettings.congestion = false
|
||||
kcpsettings.readBufferSize = 1
|
||||
kcpsettings.writeBufferSize = 1
|
||||
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
|
||||
kcpsettings.header.type = vmess.headerType
|
||||
streamSettings.kcpSettings = kcpsettings
|
||||
}
|
||||
"ws" -> {
|
||||
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
|
||||
wssettings.headers.Host = host
|
||||
}
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
wssettings.path = path
|
||||
}
|
||||
streamSettings.wsSettings = wssettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
tlssettings.serverName = host
|
||||
}
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"h2" -> {
|
||||
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
httpsettings.host = host.split(",").map { it.trim() }
|
||||
}
|
||||
httpsettings.path = path
|
||||
streamSettings.httpSettings = httpsettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
quicsettings.security = host
|
||||
quicsettings.key = path
|
||||
|
||||
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
|
||||
quicsettings.header.type = vmess.headerType
|
||||
|
||||
streamSettings.quicSettings = quicsettings
|
||||
}
|
||||
else -> {
|
||||
//tcp带http伪装
|
||||
if (vmess.headerType == "http") {
|
||||
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
|
||||
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
|
||||
tcpSettings.header.type = vmess.headerType
|
||||
|
||||
// if (requestObj.has("headers")
|
||||
// || requestObj.optJSONObject("headers").has("Pragma")) {
|
||||
// val arrHost = ArrayList<String>()
|
||||
// vmess.requestHost
|
||||
// .split(",")
|
||||
// .forEach {
|
||||
// arrHost.add(it)
|
||||
// }
|
||||
// requestObj.optJSONObject("headers")
|
||||
// .put("Host", arrHost)
|
||||
//
|
||||
// }
|
||||
if (!TextUtils.isEmpty(vmess.requestHost)) {
|
||||
val arrHost = ArrayList<String>()
|
||||
vmess.requestHost
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrHost.add("\"$it\"")
|
||||
}
|
||||
requestObj.getAsJsonObject("headers")
|
||||
.add("Host", Gson().fromJson(arrHost.toString(), JsonArray::class.java))
|
||||
}
|
||||
if (!TextUtils.isEmpty(vmess.path)) {
|
||||
val arrPath = ArrayList<String>()
|
||||
vmess.path
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrPath.add("\"$it\"")
|
||||
}
|
||||
requestObj.add("path", Gson().fromJson(arrPath.toString(), JsonArray::class.java))
|
||||
}
|
||||
tcpSettings.header.request = requestObj
|
||||
//tcpSettings.header.response = responseObj
|
||||
streamSettings.tcpSettings = tcpSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return streamSettings
|
||||
}
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* routing
|
||||
*/
|
||||
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) ?: "", AppConfig.TAG_AGENT, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: "", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) ?: "", AppConfig.TAG_BLOCKED, v2rayConfig)
|
||||
|
||||
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
|
||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "IPIfNonMatch"
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
|
||||
// Hardcode googleapis.cn
|
||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
domain = arrayListOf("domain:googleapis.cn")
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
domain = arrayListOf("domain:googleapis.cn")
|
||||
)
|
||||
|
||||
when (routingMode) {
|
||||
"0" -> {
|
||||
}
|
||||
"1" -> {
|
||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
}
|
||||
@@ -458,21 +195,19 @@ object V2rayConfigUtil {
|
||||
rulesIP.outboundTag = tag
|
||||
rulesIP.ip = ArrayList<String>()
|
||||
|
||||
userRule.trim().replace("\n", "")
|
||||
.split(",")
|
||||
.forEach {
|
||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (it.isNotBlank() || it.isNotEmpty())
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (it.isNotEmpty())
|
||||
// if (Utils.isValidUrl(it)
|
||||
// || it.startsWith("geosite:")
|
||||
// || it.startsWith("regexp:")
|
||||
// || it.startsWith("domain:")
|
||||
// || it.startsWith("full:"))
|
||||
{
|
||||
rulesDomain.domain?.add(it)
|
||||
}
|
||||
}
|
||||
{
|
||||
rulesDomain.domain?.add(it)
|
||||
}
|
||||
}
|
||||
if (rulesDomain.domain?.size!! > 0) {
|
||||
v2rayConfig.routing.rules.add(rulesDomain)
|
||||
}
|
||||
@@ -487,9 +222,8 @@ object V2rayConfigUtil {
|
||||
|
||||
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
||||
val domain = ArrayList<String>()
|
||||
userRule.trim().replace("\n", "").split(",").forEach {
|
||||
if ((it.startsWith("geosite:") || it.startsWith("domain:")) &&
|
||||
it.isNotBlank() && it.isNotEmpty()) {
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||
domain.add(it)
|
||||
}
|
||||
}
|
||||
@@ -499,33 +233,36 @@ object V2rayConfigUtil {
|
||||
/**
|
||||
* Custom Dns
|
||||
*/
|
||||
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val hosts = mutableMapOf<String, String>()
|
||||
val servers = ArrayList<Any>()
|
||||
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
|
||||
val remoteDns = Utils.getRemoteDnsServers()
|
||||
|
||||
remoteDns.forEach {
|
||||
servers.add(it)
|
||||
}
|
||||
|
||||
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
|
||||
val domesticDns = Utils.getDomesticDnsServers()
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
val geoipCn = arrayListOf("geoip:cn")
|
||||
|
||||
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
|
||||
val agDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) ?: "")
|
||||
if (agDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain))
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain, null))
|
||||
}
|
||||
|
||||
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
|
||||
val dirDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: "")
|
||||
if (dirDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain))
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain, geoipCn))
|
||||
}
|
||||
|
||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
if (routingMode == "2" || routingMode == "3") {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, arrayListOf("geosite:cn")))
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
|
||||
}
|
||||
|
||||
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
|
||||
val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) ?: "")
|
||||
if (blkDomain.size > 0) {
|
||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||
}
|
||||
@@ -540,53 +277,57 @@ object V2rayConfigUtil {
|
||||
|
||||
// DNS inbound对象
|
||||
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||
address = remoteDns.first(),
|
||||
port = 53,
|
||||
network = "tcp,udp")
|
||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||
address = if (remoteDns.first().startsWith("https")) "1.1.1.1" else remoteDns.first(),
|
||||
port = 53,
|
||||
network = "tcp,udp")
|
||||
|
||||
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
|
||||
v2rayConfig.inbounds.add(
|
||||
V2rayConfig.InboundBean(
|
||||
tag = "dns-in",
|
||||
port = 10807,
|
||||
listen = "127.0.0.1",
|
||||
protocol = "dokodemo-door",
|
||||
settings = dnsInboundSettings,
|
||||
sniffing = null))
|
||||
V2rayConfig.InboundBean(
|
||||
tag = "dns-in",
|
||||
port = 10807,
|
||||
listen = "127.0.0.1",
|
||||
protocol = "dokodemo-door",
|
||||
settings = dnsInboundSettings,
|
||||
sniffing = null))
|
||||
}
|
||||
|
||||
// DNS outbound对象
|
||||
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
|
||||
v2rayConfig.outbounds.add(
|
||||
V2rayConfig.OutboundBean(
|
||||
protocol = "dns",
|
||||
tag = "dns-out",
|
||||
settings = null,
|
||||
streamSettings = null,
|
||||
mux = null))
|
||||
V2rayConfig.OutboundBean(
|
||||
protocol = "dns",
|
||||
tag = "dns-out",
|
||||
settings = null,
|
||||
streamSettings = null,
|
||||
mux = null))
|
||||
}
|
||||
|
||||
// DNS routing
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
port = "53",
|
||||
ip = domesticDns,
|
||||
domain = null)
|
||||
)
|
||||
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
port = "53",
|
||||
ip = remoteDns,
|
||||
domain = null)
|
||||
)
|
||||
if (!domesticDns.first().startsWith("https")) {
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
port = "53",
|
||||
ip = arrayListOf(domesticDns.first()),
|
||||
domain = null)
|
||||
)
|
||||
}
|
||||
if (!remoteDns.first().startsWith("https")) {
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
port = "53",
|
||||
ip = arrayListOf(remoteDns.first()),
|
||||
domain = null)
|
||||
)
|
||||
}
|
||||
|
||||
// DNS routing tag
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
inboundTag = arrayListOf<String>("dns-in"),
|
||||
inboundTag = arrayListOf("dns-in"),
|
||||
outboundTag = "dns-out",
|
||||
domain = null)
|
||||
)
|
||||
@@ -601,112 +342,22 @@ object V2rayConfigUtil {
|
||||
/**
|
||||
* Custom Remote Dns
|
||||
*/
|
||||
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun customRemoteDns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val servers = ArrayList<Any>()
|
||||
val hosts = mutableMapOf<String, String>()
|
||||
|
||||
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
|
||||
Utils.getRemoteDnsServers().forEach {
|
||||
servers.add(it)
|
||||
}
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
hosts.put("domain:googleapis.cn", "googleapis.com")
|
||||
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers, hosts = hosts)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* is valid config
|
||||
*/
|
||||
fun isValidConfig(conf: String): Boolean {
|
||||
try {
|
||||
val jObj = JSONObject(conf)
|
||||
var hasBound = false
|
||||
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
|
||||
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
|
||||
return hasBound
|
||||
} catch (e: JSONException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
|
||||
try {
|
||||
val jObj = JSONObject(jsonConfig)
|
||||
var domainName = ""
|
||||
val tags = LinkedHashSet<String>()
|
||||
if (jObj.has("outbound")) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
|
||||
domainName = domain
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
if (jObj.has("outbounds")) {
|
||||
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jObj.has("outboundDetour")) {
|
||||
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
|
||||
val tag = if (outbound.has("tag")) {
|
||||
outbound.getString("tag")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
try {
|
||||
if (outbound.has("settings")) {
|
||||
val vnext: JSONArray?
|
||||
if (outbound.optJSONObject("settings").has("vnext")) {
|
||||
// vmess
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
|
||||
} else if (outbound.optJSONObject("settings").has("servers")) {
|
||||
// shadowsocks or socks
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
|
||||
} else {
|
||||
return Pair("", tag)
|
||||
}
|
||||
for (i in 0..(vnext.length() - 1)) {
|
||||
val item = vnext.getJSONObject(i)
|
||||
val address = item.getString("address")
|
||||
val port = item.getString("port")
|
||||
if(Utils.isIpv6Address(address)) {
|
||||
return Pair(String.format("[%s]:%s", address, port), tag)
|
||||
} else {
|
||||
return Pair(String.format("%s:%s", address, port), tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return Pair("", tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,25 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import com.v2ray.ang.util.*
|
||||
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
var serverList= MmkvManager.decodeServerList()
|
||||
private set
|
||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||
@@ -34,31 +41,52 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
override fun onCleared() {
|
||||
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
Utils.closeAllTcpSockets()
|
||||
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun reloadServerList() {
|
||||
serverList = MmkvManager.decodeServerList()
|
||||
updateListAction.value = -1
|
||||
}
|
||||
|
||||
fun removeServer(guid: String) {
|
||||
serverList.remove(guid)
|
||||
MmkvManager.removeServer(guid)
|
||||
}
|
||||
|
||||
fun appendCustomConfigServer(server: String) {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.remarks = System.currentTimeMillis().toString()
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
serverList.add(MmkvManager.encodeServerConfig("", config))
|
||||
}
|
||||
|
||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||
Collections.swap(serverList, fromPosition, toPosition)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
}
|
||||
|
||||
fun testAllTcping() {
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
Utils.closeAllTcpSockets()
|
||||
for (k in 0 until AngConfigManager.configs.vmess.count()) {
|
||||
AngConfigManager.configs.vmess[k].testResult = ""
|
||||
updateListAction.value = -1 // update all
|
||||
}
|
||||
for (k in 0 until AngConfigManager.configs.vmess.count()) {
|
||||
var serverAddress = AngConfigManager.configs.vmess[k].address
|
||||
var serverPort = AngConfigManager.configs.vmess[k].port
|
||||
if (AngConfigManager.configs.vmess[k].configType == EConfigType.CUSTOM.value) {
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(getApplication(),
|
||||
AngConfigManager.configs.vmess[k].guid) ?: continue
|
||||
serverAddress = serverOutbound.getServerAddress() ?: continue
|
||||
serverPort = serverOutbound.getServerPort() ?: continue
|
||||
}
|
||||
tcpingTestScope.launch {
|
||||
AngConfigManager.configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
|
||||
it.testResult = Utils.tcping(serverAddress, serverPort)
|
||||
launch(Dispatchers.Main) {
|
||||
updateListAction.value = k
|
||||
MmkvManager.clearAllTestDelayResults()
|
||||
updateListAction.value = -1 // update all
|
||||
|
||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||
for (guid in serverList) {
|
||||
MmkvManager.decodeServerConfig(guid)?.getProxyOutbound()?.let { outbound ->
|
||||
val serverAddress = outbound.getServerAddress()
|
||||
val serverPort = outbound.getServerPort()
|
||||
if (serverAddress != null && serverPort != null) {
|
||||
tcpingTestScope.launch {
|
||||
val testResult = Utils.tcping(serverAddress, serverPort)
|
||||
launch(Dispatchers.Main) {
|
||||
MmkvManager.encodeServerTestDelayMillis(guid, testResult)
|
||||
updateListAction.value = serverList.indexOf(guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import android.arch.lifecycle.AndroidViewModel
|
||||
import android.content.SharedPreferences
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.ui.SettingsActivity.Companion
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -25,21 +26,27 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
|
||||
when(key) {
|
||||
Companion.PREF_SNIFFING_ENABLED,
|
||||
Companion.PREF_PROXY_SHARING,
|
||||
Companion.PREF_LOCAL_DNS_ENABLED,
|
||||
Companion.PREF_REMOTE_DNS,
|
||||
Companion.PREF_DOMESTIC_DNS,
|
||||
Companion.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||
Companion.PREF_ROUTING_MODE,
|
||||
AppConfig.PREF_MODE,
|
||||
AppConfig.PREF_REMOTE_DNS,
|
||||
AppConfig.PREF_DOMESTIC_DNS,
|
||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||
AppConfig.PREF_ROUTING_MODE,
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
||||
GlobalScope.launch {
|
||||
if (!AngConfigManager.genStoreV2rayConfig()) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "$key changed but generate full configuration failed!")
|
||||
}
|
||||
}
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT, -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
||||
}
|
||||
AppConfig.PREF_SPEED_ENABLED,
|
||||
AppConfig.PREF_SNIFFING_ENABLED,
|
||||
AppConfig.PREF_PROXY_SHARING,
|
||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||
AppConfig.PREF_FORWARD_IPV6,
|
||||
AppConfig.PREF_PER_APP_PROXY,
|
||||
AppConfig.PREF_BYPASS_APPS, -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
AppConfig.PREF_PER_APP_PROXY_SET -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
android:layout_weight="1"
|
||||
android:nextFocusRight="@+id/fab" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_test"
|
||||
@@ -96,7 +97,8 @@
|
||||
android:src="@drawable/ic_v_idle"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_anchorGravity="bottom|right|end" />
|
||||
app:layout_anchorGravity="bottom|right|end"
|
||||
android:nextFocusLeft="@+id/recycler_view" />
|
||||
|
||||
</com.github.jorgecastilloprz.FABProgressCircle>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
@@ -80,11 +80,51 @@
|
||||
</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" />
|
||||
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="User(Optional)" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_security"
|
||||
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="Password(Optional)" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</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>
|
||||
</ScrollView>
|
||||
@@ -143,15 +143,10 @@
|
||||
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" />
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -161,10 +156,15 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_more_function"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
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
|
||||
@@ -241,6 +241,28 @@
|
||||
|
||||
</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_allow_insecure" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_allow_insecure"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/allowinsecures" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -249,4 +271,4 @@
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
@@ -1,15 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/item_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="3dp"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
app:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info_container"
|
||||
@@ -18,34 +20,19 @@
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@+id/layout_share">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_subid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
<android.support.v7.widget.AppCompatRadioButton
|
||||
<android.support.v7.widget.AppCompatRadioButton
|
||||
android:id="@+id/btn_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -85,20 +72,31 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="5dp">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_test_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="@color/colorPing"
|
||||
android:textSize="10sp" />
|
||||
android:id="@+id/tv_subscription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="@color/colorSubscription"
|
||||
android:textSize="10sp"
|
||||
tools:text="Sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_test_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="@color/colorPing"
|
||||
android:textSize="10sp"
|
||||
tools:text="214ms" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -185,4 +183,4 @@
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="migration_success">数据迁移成功!</string>
|
||||
<string name="migration_fail">数据迁移失败啦!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">停止</string>
|
||||
@@ -41,10 +43,10 @@
|
||||
<string name="server_lab_network">传输协议(network)</string>
|
||||
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string>
|
||||
<string name="server_lab_head_type">伪装类型(type)</string>
|
||||
<string name="server_lab_request_host">伪装域名host(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥</string>
|
||||
<string name="server_lab_stream_security">底层传输安全</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/grpc serviceName</string>
|
||||
<string name="server_lab_stream_security">底层传输安全(tls)</string>
|
||||
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||
<string name="server_lab_address3">服务器地址</string>
|
||||
<string name="server_lab_port3">服务器端口</string>
|
||||
<string name="server_lab_id3">密码</string>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="migration_success">數據遷移成功!</string>
|
||||
<string name="migration_fail">數據遷移失敗啦!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">停止</string>
|
||||
@@ -41,10 +43,10 @@
|
||||
<string name="server_lab_network">網路</string>
|
||||
<string name="server_lab_more_function">更多功能</string>
|
||||
<string name="server_lab_head_type">標頭類型</string>
|
||||
<string name="server_lab_request_host">要求主機(host/ws host/h2 host)/QUIC加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全性</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_request_host">要求主機(host)(host/ws host/h2 host)/QUIC加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰/kcp seed/grpc serviceName</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全性(tls)</string>
|
||||
<string name="server_lab_allow_insecure">跳過證書驗證(allowInsecure)</string>
|
||||
<string name="server_lab_address3">伺服器位址</string>
|
||||
<string name="server_lab_port3">伺服器埠</string>
|
||||
<string name="server_lab_id3">密碼</string>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<item>aes-128-gcm</item>
|
||||
<item>auto</item>
|
||||
<item>none</item>
|
||||
<item>zero</item>
|
||||
</string-array>
|
||||
<string-array name="ss_securitys" translatable="false">
|
||||
<item>aes-256-cfb</item>
|
||||
@@ -23,6 +24,7 @@
|
||||
<item>ws</item>
|
||||
<item>h2</item>
|
||||
<item>quic</item>
|
||||
<item>grpc</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="headertypes" translatable="false">
|
||||
@@ -53,6 +55,17 @@
|
||||
<item></item>
|
||||
<item>tls</item>
|
||||
</string-array>
|
||||
<string-array name="streamsecurityxs" translatable="false">
|
||||
<item></item>
|
||||
<item>tls</item>
|
||||
<item>xtls</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="allowinsecures" translatable="false">
|
||||
<item></item>
|
||||
<item>true</item>
|
||||
<item>false</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode_value" translatable="false">
|
||||
<item>0</item>
|
||||
@@ -67,11 +80,30 @@
|
||||
<item>IPOnDemand</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="core_loglevel" translatable="false">
|
||||
<item>debug</item>
|
||||
<item>info</item>
|
||||
<item>warning</item>
|
||||
<item>error</item>
|
||||
<item>none</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_value" translatable="false">
|
||||
<item>VPN</item>
|
||||
<item>Proxy only</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="flows" translatable="false">
|
||||
<item></item>
|
||||
<item>xtls-rprx-origin</item>
|
||||
<item>xtls-rprx-origin-udp443</item>
|
||||
<item>xtls-rprx-direct</item>
|
||||
<item>xtls-rprx-direct-udp443</item>
|
||||
<item>xtls-rprx-splice</item>
|
||||
<item>xtls-rprx-splice-udp443</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<!-- minimum list https://serverfault.com/a/304791 -->
|
||||
<string-array name="bypass_private_ip_address" translatable="false">
|
||||
<item>0.0.0.0/5</item>
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
<color name="icons">#FFFFFF</color>
|
||||
<color name="divider">#BDBDBD</color>
|
||||
<color name="colorPing">#185534</color>
|
||||
<color name="colorSubscription">#247BA0</color>
|
||||
</resources>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="migration_success">Data migration success!</string>
|
||||
<string name="migration_fail">Data migration failed!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">Stop</string>
|
||||
@@ -42,7 +44,7 @@
|
||||
<string name="server_lab_more_function">more function</string>
|
||||
<string name="server_lab_head_type">head type</string>
|
||||
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key/kcp seed/grpc serviceName</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_address3">address</string>
|
||||
|
||||
1
V2rayNG/dpreference/.gitignore
vendored
1
V2rayNG/dpreference/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,25 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||
buildToolsVersion buildToolsVer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 17
|
||||
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
testImplementation 'junit:junit:4.13'
|
||||
implementation "com.android.support:support-annotations:$supportLibVersion"
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
}
|
||||
17
V2rayNG/dpreference/proguard-rules.pro
vendored
17
V2rayNG/dpreference/proguard-rules.pro
vendored
@@ -1,17 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/wangyida/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@@ -1,16 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.dozen.dpreference">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<provider
|
||||
android:name="me.dozen.dpreference.PreferenceProvider"
|
||||
android:enabled="true"
|
||||
android:authorities="com.v2ray.ang.dpreference"
|
||||
android:exported="false" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,86 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by wangyida on 15-4-9.
|
||||
*/
|
||||
public class DPreference {
|
||||
|
||||
Context mContext;
|
||||
|
||||
/**
|
||||
* preference file name
|
||||
*/
|
||||
String mName;
|
||||
|
||||
private DPreference() {
|
||||
}
|
||||
|
||||
public DPreference(Context context, String name) {
|
||||
this.mContext = context;
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
public String getPrefString(final String key, final String defaultValue) {
|
||||
return PrefAccessor.getString(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefString(final String key, final String value) {
|
||||
PrefAccessor.setString(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public boolean getPrefBoolean(final String key, final boolean defaultValue) {
|
||||
return PrefAccessor.getBoolean(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefBoolean(final String key, final boolean value) {
|
||||
PrefAccessor.setBoolean(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public void setPrefInt(final String key, final int value) {
|
||||
PrefAccessor.setInt(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public int getPrefInt(final String key, final int defaultValue) {
|
||||
return PrefAccessor.getInt(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefLong(final String key, final long value) {
|
||||
PrefAccessor.setLong(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public long getPrefLong(final String key, final long defaultValue) {
|
||||
return PrefAccessor.getLong(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefStringSet(final String key, final Set<String> value) {
|
||||
PrefAccessor.setStringSet(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public Set<String> getPrefStringSet(final String key, final Set<String> defaultValue) {
|
||||
return PrefAccessor.getStringSet(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefStringOrderedSet(final String key, final LinkedHashSet<String> value) {
|
||||
PrefAccessor.setString(mContext, mName, key, StringSetConverter.encode(value));
|
||||
}
|
||||
|
||||
public LinkedHashSet<String> getPrefStringOrderedSet(final String key, final LinkedHashSet<String> defaultValue) {
|
||||
String value = PrefAccessor.getString(mContext, mName, key, "");
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return StringSetConverter.decode(value);
|
||||
}
|
||||
|
||||
public void removePreference(final String key) {
|
||||
PrefAccessor.remove(mContext, mName, key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
final class IOUtils {
|
||||
|
||||
// NOTE: This class is focussed on InputStream, OutputStream, Reader and
|
||||
// Writer. Each method should take at least one of these as a parameter,
|
||||
// or return one of them.
|
||||
|
||||
private static final int EOF = -1;
|
||||
|
||||
/**
|
||||
* The default buffer size ({@value}) to use for {@link
|
||||
*/
|
||||
private static final int DEFAULT_BUFFER_SIZE = 1024;
|
||||
|
||||
public static void closeQuietly(InputStream is) {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(OutputStream os) {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(Reader r) {
|
||||
if (r != null) {
|
||||
try {
|
||||
r.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(Cursor cursor) {
|
||||
if (cursor != null && !cursor.isClosed()) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by wangyida on 15/12/18.
|
||||
*/
|
||||
interface IPrefImpl {
|
||||
|
||||
String getPrefString(String key, String defaultValue);
|
||||
|
||||
void setPrefString(String key, String value);
|
||||
|
||||
boolean getPrefBoolean(String key, boolean defaultValue);
|
||||
|
||||
void setPrefBoolean(final String key, final boolean value);
|
||||
|
||||
void setPrefInt(final String key, final int value);
|
||||
|
||||
int getPrefInt(final String key, final int defaultValue);
|
||||
|
||||
void setPrefFloat(final String key, final float value);
|
||||
|
||||
float getPrefFloat(final String key, final float defaultValue);
|
||||
|
||||
void setPrefLong(final String key, final long value);
|
||||
|
||||
long getPrefLong(final String key, final long defaultValue);
|
||||
|
||||
Set<String> getPrefStringSet(String key, Set<String> defaultValue);
|
||||
|
||||
void setPrefStringSet(String key, Set<String> value);
|
||||
|
||||
void removePreference(final String key);
|
||||
|
||||
boolean hasKey(String key);
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by wangyida on 15/12/18.
|
||||
*/
|
||||
class PrefAccessor {
|
||||
|
||||
public static String getString(Context context, String name, String key, String defaultValue) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
|
||||
String value = defaultValue;
|
||||
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
value = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
|
||||
}
|
||||
IOUtils.closeQuietly(cursor);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int getInt(Context context, String name, String key, int defaultValue) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_INT);
|
||||
int value = defaultValue;
|
||||
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
value = cursor.getInt(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
|
||||
}
|
||||
IOUtils.closeQuietly(cursor);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static long getLong(Context context, String name, String key, long defaultValue) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_LONG);
|
||||
long value = defaultValue;
|
||||
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
value = cursor.getLong(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
|
||||
}
|
||||
IOUtils.closeQuietly(cursor);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static boolean getBoolean(Context context, String name, String key, boolean defaultValue) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_BOOLEAN);
|
||||
int value = defaultValue ? 1 : 0;
|
||||
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
value = cursor.getInt(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
|
||||
}
|
||||
IOUtils.closeQuietly(cursor);
|
||||
return value == 1;
|
||||
}
|
||||
|
||||
public static Set<String> getStringSet(Context context, String name, String key, Set<String> defaultValue) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING_SET);
|
||||
Set<String> value = defaultValue;
|
||||
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String cursorString = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
|
||||
value = StringSetConverter.decode(cursorString);
|
||||
}
|
||||
IOUtils.closeQuietly(cursor);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void remove(Context context, String name, String key) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
|
||||
context.getContentResolver().delete(URI, null, null);
|
||||
}
|
||||
|
||||
public static void setString(Context context, String name, String key, String value) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(PreferenceProvider.PREF_KEY, key);
|
||||
cv.put(PreferenceProvider.PREF_VALUE, value);
|
||||
context.getContentResolver().update(URI, cv, null, null);
|
||||
}
|
||||
|
||||
public static void setBoolean(Context context, String name, String key, boolean value) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_BOOLEAN);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(PreferenceProvider.PREF_KEY, key);
|
||||
cv.put(PreferenceProvider.PREF_VALUE, value);
|
||||
context.getContentResolver().update(URI, cv, null, null);
|
||||
}
|
||||
|
||||
public static void setInt(Context context, String name, String key, int value) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_INT);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(PreferenceProvider.PREF_KEY, key);
|
||||
cv.put(PreferenceProvider.PREF_VALUE, value);
|
||||
context.getContentResolver().update(URI, cv, null, null);
|
||||
}
|
||||
|
||||
public static void setLong(Context context, String name, String key, long value) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_LONG);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(PreferenceProvider.PREF_KEY, key);
|
||||
cv.put(PreferenceProvider.PREF_VALUE, value);
|
||||
context.getContentResolver().update(URI, cv, null, null);
|
||||
}
|
||||
|
||||
public static void setStringSet(Context context, String name, String key, Set<String> value) {
|
||||
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING_SET);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(PreferenceProvider.PREF_KEY, key);
|
||||
cv.put(PreferenceProvider.PREF_VALUE, StringSetConverter.encode(value));
|
||||
context.getContentResolver().update(URI, cv, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by wangyida on 15/12/18.
|
||||
*/
|
||||
class PreferenceImpl implements IPrefImpl {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private String mPrefName;
|
||||
|
||||
public PreferenceImpl(Context context, String prefName) {
|
||||
mContext = context;
|
||||
mPrefName = prefName;
|
||||
}
|
||||
|
||||
public String getPrefString(final String key,
|
||||
final String defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getString(key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefString(final String key, final String value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putString(key, value).apply();
|
||||
}
|
||||
|
||||
public boolean getPrefBoolean(final String key,
|
||||
final boolean defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
public boolean hasKey(final String key) {
|
||||
return mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE)
|
||||
.contains(key);
|
||||
}
|
||||
|
||||
public void setPrefBoolean(final String key, final boolean value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putBoolean(key, value).apply();
|
||||
}
|
||||
|
||||
public void setPrefInt(final String key, final int value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPrefStringSet(String key, Set<String> defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getStringSet(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefStringSet(String key, Set<String> value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putStringSet(key, value).apply();
|
||||
}
|
||||
|
||||
public void increasePrefInt(final String key) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
increasePrefInt(settings, key);
|
||||
}
|
||||
|
||||
public void increasePrefInt(final SharedPreferences sp, final String key) {
|
||||
final int v = sp.getInt(key, 0) + 1;
|
||||
sp.edit().putInt(key, v).apply();
|
||||
}
|
||||
|
||||
public void increasePrefInt(final SharedPreferences sp, final String key,
|
||||
final int increment) {
|
||||
final int v = sp.getInt(key, 0) + increment;
|
||||
sp.edit().putInt(key, v).apply();
|
||||
}
|
||||
|
||||
public int getPrefInt(final String key, final int defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getInt(key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefFloat(final String key, final float value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putFloat(key, value).apply();
|
||||
}
|
||||
|
||||
public float getPrefFloat(final String key, final float defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getFloat(key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefLong(final String key, final long value) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
settings.edit().putLong(key, value).apply();
|
||||
}
|
||||
|
||||
public long getPrefLong(final String key, final long defaultValue) {
|
||||
final SharedPreferences settings =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
return settings.getLong(key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
public void removePreference(final String key) {
|
||||
final SharedPreferences prefs =
|
||||
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
public void clearPreference(final SharedPreferences p) {
|
||||
final SharedPreferences.Editor editor = p.edit();
|
||||
editor.clear();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by wangyida on 15/12/18.
|
||||
*/
|
||||
public class PreferenceProvider extends ContentProvider {
|
||||
|
||||
private static final String TAG = PreferenceProvider.class.getSimpleName();
|
||||
|
||||
private static final String AUTHORITY = "com.v2ray.ang.dpreference";
|
||||
|
||||
public static final String CONTENT_PREF_BOOLEAN_URI = "content://" + AUTHORITY + "/boolean/";
|
||||
public static final String CONTENT_PREF_STRING_URI = "content://" + AUTHORITY + "/string/";
|
||||
public static final String CONTENT_PREF_INT_URI = "content://" + AUTHORITY + "/integer/";
|
||||
public static final String CONTENT_PREF_LONG_URI = "content://" + AUTHORITY + "/long/";
|
||||
public static final String CONTENT_PREF_STRING_SET_URI = "content://" + AUTHORITY + "/string_set/";
|
||||
|
||||
|
||||
public static final String PREF_KEY = "key";
|
||||
public static final String PREF_VALUE = "value";
|
||||
|
||||
public static final int PREF_BOOLEAN = 1;
|
||||
public static final int PREF_STRING = 2;
|
||||
public static final int PREF_INT = 3;
|
||||
public static final int PREF_LONG = 4;
|
||||
public static final int PREF_STRING_SET = 5;
|
||||
|
||||
private static final UriMatcher sUriMatcher;
|
||||
|
||||
static {
|
||||
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
sUriMatcher.addURI(AUTHORITY, "boolean/*/*", PREF_BOOLEAN);
|
||||
sUriMatcher.addURI(AUTHORITY, "string/*/*", PREF_STRING);
|
||||
sUriMatcher.addURI(AUTHORITY, "integer/*/*", PREF_INT);
|
||||
sUriMatcher.addURI(AUTHORITY, "long/*/*", PREF_LONG);
|
||||
sUriMatcher.addURI(AUTHORITY, "string_set/*/*", PREF_STRING_SET);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
MatrixCursor cursor = null;
|
||||
PrefModel model = getPrefModelByUri(uri);
|
||||
switch (sUriMatcher.match(uri)) {
|
||||
case PREF_BOOLEAN:
|
||||
if (getDPreference(model.getName()).hasKey(model.getKey())) {
|
||||
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefBoolean(model.getKey(), false) ? 1 : 0);
|
||||
}
|
||||
break;
|
||||
case PREF_STRING:
|
||||
if (getDPreference(model.getName()).hasKey(model.getKey())) {
|
||||
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefString(model.getKey(), ""));
|
||||
}
|
||||
break;
|
||||
case PREF_INT:
|
||||
if (getDPreference(model.getName()).hasKey(model.getKey())) {
|
||||
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefInt(model.getKey(), -1));
|
||||
}
|
||||
break;
|
||||
case PREF_LONG:
|
||||
if (getDPreference(model.getName()).hasKey(model.getKey())) {
|
||||
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefLong(model.getKey(), -1));
|
||||
}
|
||||
break;
|
||||
case PREF_STRING_SET:
|
||||
if (getDPreference(model.getName()).hasKey(model.getKey())) {
|
||||
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefStringSet(model.getKey(), null));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new IllegalStateException("insert unsupport!!!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
switch (sUriMatcher.match(uri)) {
|
||||
case PREF_BOOLEAN:
|
||||
case PREF_LONG:
|
||||
case PREF_STRING:
|
||||
case PREF_INT:
|
||||
case PREF_STRING_SET:
|
||||
PrefModel model = getPrefModelByUri(uri);
|
||||
if (model != null) {
|
||||
getDPreference(model.getName()).removePreference(model.getKey());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(" unsupported uri : " + uri);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
PrefModel model = getPrefModelByUri(uri);
|
||||
if (model == null) {
|
||||
throw new IllegalArgumentException("update prefModel is null");
|
||||
}
|
||||
switch (sUriMatcher.match(uri)) {
|
||||
case PREF_BOOLEAN:
|
||||
persistBoolean(model.getName(), values);
|
||||
break;
|
||||
case PREF_LONG:
|
||||
persistLong(model.getName(), values);
|
||||
break;
|
||||
case PREF_STRING:
|
||||
persistString(model.getName(), values);
|
||||
break;
|
||||
case PREF_INT:
|
||||
persistInt(model.getName(), values);
|
||||
break;
|
||||
case PREF_STRING_SET:
|
||||
persistStringSet(model.getName(), values);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("update unsupported uri : " + uri);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String[] PREFERENCE_COLUMNS = {PREF_VALUE};
|
||||
|
||||
private <T> MatrixCursor preferenceToCursor(T value) {
|
||||
MatrixCursor matrixCursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);
|
||||
MatrixCursor.RowBuilder builder = matrixCursor.newRow();
|
||||
builder.add(value);
|
||||
return matrixCursor;
|
||||
}
|
||||
|
||||
private void persistInt(String name, ContentValues values) {
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException(" values is null!!!");
|
||||
}
|
||||
String kInteger = values.getAsString(PREF_KEY);
|
||||
int vInteger = values.getAsInteger(PREF_VALUE);
|
||||
getDPreference(name).setPrefInt(kInteger, vInteger);
|
||||
}
|
||||
|
||||
private void persistBoolean(String name, ContentValues values) {
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException(" values is null!!!");
|
||||
}
|
||||
String kBoolean = values.getAsString(PREF_KEY);
|
||||
boolean vBoolean = values.getAsBoolean(PREF_VALUE);
|
||||
getDPreference(name).setPrefBoolean(kBoolean, vBoolean);
|
||||
}
|
||||
|
||||
private void persistLong(String name, ContentValues values) {
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException(" values is null!!!");
|
||||
}
|
||||
String kLong = values.getAsString(PREF_KEY);
|
||||
long vLong = values.getAsLong(PREF_VALUE);
|
||||
getDPreference(name).setPrefLong(kLong, vLong);
|
||||
}
|
||||
|
||||
private void persistString(String name, ContentValues values) {
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException(" values is null!!!");
|
||||
}
|
||||
String kString = values.getAsString(PREF_KEY);
|
||||
String vString = values.getAsString(PREF_VALUE);
|
||||
getDPreference(name).setPrefString(kString, vString);
|
||||
}
|
||||
|
||||
private void persistStringSet(String name, ContentValues values) {
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException(" values is null!!!");
|
||||
}
|
||||
String kString = values.getAsString(PREF_KEY);
|
||||
String vString = values.getAsString(PREF_VALUE);
|
||||
getDPreference(name).setPrefStringSet(kString, StringSetConverter.decode(vString));
|
||||
}
|
||||
|
||||
private static Map<String, IPrefImpl> sPreferences = new HashMap<>();
|
||||
|
||||
private IPrefImpl getDPreference(String name) {
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
throw new IllegalArgumentException("getDPreference name is null!!!");
|
||||
}
|
||||
if (sPreferences.get(name) == null) {
|
||||
IPrefImpl pref = new PreferenceImpl(getContext(), name);
|
||||
sPreferences.put(name, pref);
|
||||
}
|
||||
return sPreferences.get(name);
|
||||
}
|
||||
|
||||
private PrefModel getPrefModelByUri(Uri uri) {
|
||||
if (uri == null || uri.getPathSegments().size() != 3) {
|
||||
throw new IllegalArgumentException("getPrefModelByUri uri is wrong : " + uri);
|
||||
}
|
||||
String name = uri.getPathSegments().get(1);
|
||||
String key = uri.getPathSegments().get(2);
|
||||
return new PrefModel(name, key);
|
||||
}
|
||||
|
||||
|
||||
public static Uri buildUri(String name, String key, int type) {
|
||||
return Uri.parse(getUriByType(type) + name + "/" + key);
|
||||
}
|
||||
|
||||
private static String getUriByType(int type) {
|
||||
switch (type) {
|
||||
case PreferenceProvider.PREF_BOOLEAN:
|
||||
return PreferenceProvider.CONTENT_PREF_BOOLEAN_URI;
|
||||
case PreferenceProvider.PREF_INT:
|
||||
return PreferenceProvider.CONTENT_PREF_INT_URI;
|
||||
case PreferenceProvider.PREF_LONG:
|
||||
return PreferenceProvider.CONTENT_PREF_LONG_URI;
|
||||
case PreferenceProvider.PREF_STRING:
|
||||
return PreferenceProvider.CONTENT_PREF_STRING_URI;
|
||||
case PreferenceProvider.PREF_STRING_SET:
|
||||
return PreferenceProvider.CONTENT_PREF_STRING_SET_URI;
|
||||
}
|
||||
throw new IllegalStateException("unsupport preftype : " + type);
|
||||
}
|
||||
|
||||
private static class PrefModel {
|
||||
String name;
|
||||
|
||||
String key;
|
||||
|
||||
public PrefModel(String name, String key) {
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package me.dozen.dpreference;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class StringSetConverter {
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
public static String encode(Set<String> src) {
|
||||
return gson.toJson(src);
|
||||
}
|
||||
|
||||
public static LinkedHashSet<String> decode(String json) {
|
||||
Type setType = new TypeToken<LinkedHashSet<String>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(json, setType);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
include ':app', ':dpreference'
|
||||
include ':app'
|
||||
|
||||
Reference in New Issue
Block a user