Move main storage references to MMKV

- Move current index to MMKV
- Move ServiceManager to MMKV
- Fix isRunning in ServerActivity
- Drop support for VMESS qrcode v1
- Change all protocols importing
This commit is contained in:
yuhan6665
2021-02-14 18:54:51 -05:00
parent 872f9ce199
commit 375a209beb
17 changed files with 463 additions and 734 deletions
@@ -1,9 +1,14 @@
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,
val subscriptionId: String = "",
var subscriptionId: String = "",
val addedTime: Long = System.currentTimeMillis(),
var remarks: String = "",
val outboundBean: V2rayConfig.OutboundBean? = null,
@@ -48,4 +53,24 @@ data class ServerConfig(
}
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)
}
}
}
@@ -1,5 +1,7 @@
package com.v2ray.ang.dto
import android.text.TextUtils
data class V2rayConfig(
val stats: Any? = null,
val log: LogBean,
@@ -112,7 +114,7 @@ data class V2rayConfig(
var readBufferSize: Int = 1,
var writeBufferSize: Int = 1,
var header: HeaderBean = HeaderBean(),
var seed: String? = "") {
var seed: String? = null) {
data class HeaderBean(var type: String = "none")
}
@@ -132,6 +134,72 @@ data class V2rayConfig(
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none")
}
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?): String {
var sni = ""
network = transport
when (network) {
"tcp" -> if (headerType == HTTP) {
val tcpSetting = TcpSettingsBean()
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
}
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
}
}
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, var concurrency: Int = 8)
@@ -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,16 +13,20 @@ 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.dto.ServerConfig
import com.v2ray.ang.extension.defaultDPreference
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.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
@@ -40,6 +44,7 @@ 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) }
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
@@ -51,7 +56,7 @@ object V2RayServiceManager {
Seq.setContext(context)
}
}
var currentConfigName = "NG"
var currentConfig: ServerConfig? = null
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
@@ -122,7 +127,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)
@@ -134,12 +144,12 @@ object V2RayServiceManager {
Log.d(service.packageName, e.toString())
}
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
currentConfig = config
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(AppConfig.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")
try {
v2rayPoint.runLoop()
@@ -243,7 +253,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)
@@ -280,7 +290,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)
@@ -309,8 +319,8 @@ object V2RayServiceManager {
v2rayPoint.isRunning &&
service.defaultDPreference.getPrefBoolean(AppConfig.PREF_SPEED_ENABLED, false)) {
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 {
@@ -318,7 +328,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) {
@@ -331,7 +341,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)
@@ -357,7 +367,7 @@ object V2RayServiceManager {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
updateNotification(currentConfigName, 0, 0)
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}
@@ -126,7 +126,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
builder.setSession(V2RayServiceManager.currentConfigName)
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(AppConfig.PREF_PER_APP_PROXY, false)) {
@@ -17,6 +17,7 @@ import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
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
@@ -24,7 +25,9 @@ import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference
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
@@ -47,6 +50,7 @@ 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 var mItemTouchHelper: ItemTouchHelper? = null
val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
@@ -110,7 +114,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
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)
@@ -126,19 +130,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
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.notifyDataSetChanged()
mainViewModel.reloadServerList()
}
public override fun onPause() {
@@ -437,9 +440,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()
@@ -453,19 +455,21 @@ 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
}
if (!V2rayConfigUtil.isValidConfig(server)) {
toast(R.string.toast_config_file_invalid)
return
}
mainViewModel.appendCustomConfigServer(server)
toast(R.string.toast_success)
adapter.notifyDataSetChanged()
adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
} catch (e: Exception) {
e.printStackTrace()
return
}
}
@@ -15,6 +15,7 @@ import com.v2ray.ang.dto.EConfigType
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
@@ -36,20 +37,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_method)
}
var changeable: Boolean = true
set(value) {
if (field == value)
return
field = value
notifyDataSetChanged()
}
var isRunning = false
override fun getItemCount() = mActivity.mainViewModel.serverList.size
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
val guid = mActivity.mainViewModel.getGuid(position) ?: return
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
val outbound = config.getProxyOutbound() ?: return
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
@@ -59,7 +53,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = aff?.getTestDelayString() ?: ""
if (aff?.testDelayMillis?:0L < 0L) {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, R.color.viewfinder_laser))
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
} else {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
}
@@ -83,7 +77,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
when (i) {
0 -> {
if (config.configType == EConfigType.CUSTOM) {
shareFullContent(position)
shareFullContent(guid)
} else {
val iv = LayoutInflater.from(mActivity).inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
@@ -97,7 +91,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
mActivity.toast(R.string.toast_failure)
}
}
2 -> shareFullContent(position)
2 -> shareFullContent(guid)
else -> mActivity.toast("else")
}
} catch (e: Exception) {
@@ -108,6 +102,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.layout_edit.setOnClickListener {
val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", isRunning)
if (config.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
@@ -115,28 +110,29 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
}
holder.layout_remove.setOnClickListener {
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
&& AngConfigManager.removeServer(position) == 0) {
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
}
}
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) {
@@ -151,8 +147,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
}
private fun shareFullContent(position: Int) {
if (AngConfigManager.shareFullContent2Clipboard(mActivity.mainViewModel.getGuid(position)) == 0) {
private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
@@ -214,12 +210,12 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
override fun onItemDismiss(position: Int) {
if (mActivity.mainViewModel.getGuid(position) != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
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()
// }
@@ -227,13 +223,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
AngConfigManager.swapServer(fromPosition, toPosition)
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
//notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
return true
}
override fun onItemMoveCompleted() {
AngConfigManager.storeConfigFile()
// do nothing
}
}
@@ -12,7 +12,6 @@ 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.HTTP
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
import com.v2ray.ang.extension.toast
@@ -37,7 +36,11 @@ class ServerActivity : BaseActivity() {
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy { editGuid.isNotEmpty() && editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER) }
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
}
@@ -237,73 +240,25 @@ class ServerActivity : BaseActivity() {
}
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
streamSetting.network = if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK
val headerType = if (sp_header_type != null) headertypes[sp_header_type.selectedItemPosition] else ""
val requestHost = et_request_host.text.toString().trim()
val path = et_path?.text.toString().trim()
var sni = requestHost
when (streamSetting.network) {
"tcp" -> if (headerType == HTTP) {
val tcpSetting = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
tcpSetting.header.type = headerType
if (!TextUtils.isEmpty(requestHost) || !TextUtils.isEmpty(path)) {
val requestObj = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean()
requestObj.headers.Host = requestHost.split(",").map { it.trim() }
requestObj.path = path.split(",").map { it.trim() }
tcpSetting.header.request = requestObj
sni = requestObj.headers.Host.getOrNull(0) ?: sni
}
streamSetting.tcpSettings = tcpSetting
}
"kcp" -> {
val kcpsetting = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
kcpsetting.header.type = headerType
if (TextUtils.isEmpty(path)) {
kcpsetting.seed = null
} else {
kcpsetting.seed = path
}
streamSetting.kcpSettings = kcpsetting
}
"ws" -> {
val wssetting = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
if (!TextUtils.isEmpty(requestHost)) {
wssetting.headers.Host = requestHost
sni = requestHost
}
if (!TextUtils.isEmpty(path)) {
wssetting.path = path
}
streamSetting.wsSettings = wssetting
}
"h2" -> {
val h2Setting = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
if (!TextUtils.isEmpty(requestHost)) {
h2Setting.host = requestHost.split(",").map { it.trim() }
sni = h2Setting.host.getOrNull(0) ?: sni
}
h2Setting.path = path
streamSetting.httpSettings = h2Setting
}
"quic" -> {
val quicsetting = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
quicsetting.security = requestHost
quicsetting.key = path
quicsetting.header.type = headerType
streamSetting.quicSettings = quicsetting
}
}
streamSetting.security = if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else TLS
val tlsSetting = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean(
allowInsecure = if (sp_allow_insecure != null) allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean() else false,
serverName = sni
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
)
if (streamSetting.security == TLS) {
streamSetting.tlsSettings = tlsSetting
} else if (streamSetting.security == XTLS) {
streamSetting.xtlsSettings = tlsSetting
if (sni.isEmpty()) {
sni = requestHost
}
streamSetting.populateTlsSettings(
if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else TLS,
if (sp_allow_insecure != null) allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean() else false,
sni
)
}
/**
@@ -21,7 +21,11 @@ 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 { editGuid.isNotEmpty() && editGuid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) }
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)
@@ -8,7 +8,7 @@ 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
@@ -56,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 {
@@ -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)
@@ -108,4 +112,3 @@ class TaskerActivity : BaseActivity() {
}
}
@@ -3,13 +3,16 @@ package com.v2ray.ang.util
import android.graphics.Bitmap
import android.text.TextUtils
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig.ANG_CONFIG
import com.v2ray.ang.AppConfig.HTTPS_PROTOCOL
import com.v2ray.ang.AppConfig.HTTP_PROTOCOL
import com.v2ray.ang.R
import com.v2ray.ang.dto.*
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import java.net.URI
import java.net.URLDecoder
import java.util.*
@@ -18,18 +21,17 @@ object AngConfigManager {
private lateinit var app: AngApplication
private lateinit var angConfig: AngConfig
val configs: AngConfig get() = angConfig
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
fun inject(app: AngApplication) {
this.app = app
if (app.firstRun) {
}
loadConfig()
}
/**
* loading config
*/
fun loadConfig() {
private fun loadConfig() {
try {
val context = app.defaultDPreference.getPrefString(ANG_CONFIG, "")
if (!TextUtils.isEmpty(context)) {
@@ -54,115 +56,6 @@ object AngConfigManager {
}
}
/**
* add or edit server
*/
fun addServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = EConfigType.VMESS.value
vmess.address = vmess.address.trim()
vmess.id = vmess.id.trim()
vmess.security = vmess.security.trim()
vmess.network = vmess.network.trim()
vmess.headerType = vmess.headerType.trim()
vmess.requestHost = vmess.requestHost.trim()
vmess.path = vmess.path.trim()
vmess.streamSecurity = vmess.streamSecurity.trim()
if (index >= 0) {
//edit
angConfig.vmess[index] = vmess
} else {
//add
vmess.guid = Utils.getUuid()
angConfig.vmess.add(vmess)
if (angConfig.vmess.count() == 1) {
angConfig.index = 0
}
}
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
/**
* 移除服务器
*/
fun removeServer(index: Int): Int {
try {
if (index < 0 || index > angConfig.vmess.count() - 1) {
return -1
}
//删除
angConfig.vmess.removeAt(index)
//移除的是活动的
adjustIndexForRemovalAt(index)
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
private fun adjustIndexForRemovalAt(index: Int) {
if (angConfig.index == index) {
if (angConfig.vmess.count() > 0) {
angConfig.index = 0
} else {
angConfig.index = -1
}
} else if (index < angConfig.index)//移除活动之前的
{
angConfig.index--
}
}
fun swapServer(fromPosition: Int, toPosition: Int): Int {
try {
Collections.swap(angConfig.vmess, fromPosition, toPosition)
val index = angConfig.index
if (index == fromPosition) {
angConfig.index = toPosition
} else if (index == toPosition) {
angConfig.index = fromPosition
}
//storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
/**
* set active server
*/
fun setActiveServer(index: Int): Int {
try {
if (index < 0 || index > angConfig.vmess.count() - 1) {
return -1
}
angConfig.index = index
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
/**
* store config to file
*/
@@ -178,73 +71,65 @@ object AngConfigManager {
/**
* import config form qrcode or...
*/
fun importConfig(server: String?, subid: String, removedSelectedServer: AngConfig.VmessBean?): Int {
private fun importConfig(str: String?, subid: String, removedSelectedServer: ServerConfig?): Int {
try {
if (server == null || TextUtils.isEmpty(server)) {
if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data
}
//maybe sub
if (server.startsWith(HTTP_PROTOCOL) || server.startsWith(HTTPS_PROTOCOL)) {
addSubItem(server)
if (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL)) {
addSubItem(str)
return 0
}
var vmess = AngConfig.VmessBean()
var config: ServerConfig? = null
val allowInsecure = false//app.defaultDPreference.getPrefBoolean(AppConfig.PREF_ALLOW_INSECURE, false)
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1
if (server.startsWith(EConfigType.VMESS.protocolScheme)) {
if (!tryParseNewVmess(str, config, allowInsecure)) {
if (str.indexOf("?") > 0) {
if (!tryResolveVmess4Kitsunebi(str, config)) {
return R.string.toast_incorrect_protocol
}
} else {
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
return R.string.toast_decoding_failed
}
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.aid)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
return R.string.toast_incorrect_protocol
}
val indexSplit = server.indexOf("?")
val newVmess = tryParseNewVmess(server)
if (newVmess != null) {
vmess = newVmess
vmess.subid = subid
} else if (indexSplit > 0) {
vmess = ResolveVmess4Kitsunebi(server)
} else {
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
return R.string.toast_decoding_failed
config.remarks = vmessQRCode.ps
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
vnext.users[0].encryption = DEFAULT_SECURITY
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host,
vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path)
streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure, sni)
}
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.aid)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
return R.string.toast_incorrect_protocol
}
vmess.configType = EConfigType.VMESS.value
vmess.security = "auto"
vmess.network = "tcp"
vmess.headerType = "none"
vmess.configVersion = Utils.parseInt(vmessQRCode.v)
vmess.remarks = vmessQRCode.ps
vmess.address = vmessQRCode.add
vmess.port = Utils.parseInt(vmessQRCode.port)
vmess.id = vmessQRCode.id
vmess.alterId = Utils.parseInt(vmessQRCode.aid)
vmess.network = vmessQRCode.net
vmess.headerType = vmessQRCode.type
vmess.requestHost = vmessQRCode.host
vmess.path = vmessQRCode.path
vmess.streamSecurity = vmessQRCode.tls
vmess.subid = subid
}
upgradeServerVersion(vmess)
addServer(vmess, -1)
} else if (server.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
var result = server.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (indexSplit > 0) {
try {
vmess.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
@@ -254,32 +139,28 @@ object AngConfigManager {
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
} else {
result = Utils.decode(result)
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
if (match == null) {
return R.string.toast_incorrect_protocol
}
vmess.security = match.groupValues[1].toLowerCase()
vmess.id = match.groupValues[2]
vmess.address = match.groupValues[3]
if (vmess.address.firstOrNull() == '[' && vmess.address.lastOrNull() == ']')
vmess.address = vmess.address.substring(1, vmess.address.length - 1)
vmess.port = match.groupValues[4].toInt()
vmess.subid = subid
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
addShadowsocksServer(vmess, -1)
} else if (server.startsWith(EConfigType.SOCKS.protocolScheme)) {
var result = server.replace(EConfigType.SOCKS.protocolScheme, "")
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
server.password = match.groupValues[2]
server.method = match.groupValues[1].toLowerCase()
}
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) {
try {
vmess.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
@@ -296,88 +177,58 @@ object AngConfigManager {
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match = legacyPattern.matchEntire(result)
if (match == null) {
return R.string.toast_incorrect_protocol
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1].toLowerCase()
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
}
vmess.security = match.groupValues[1].toLowerCase()
vmess.id = match.groupValues[2]
vmess.address = match.groupValues[3]
if (vmess.address.firstOrNull() == '[' && vmess.address.lastOrNull() == ']')
vmess.address = vmess.address.substring(1, vmess.address.length - 1)
vmess.port = match.groupValues[4].toInt()
vmess.subid = subid
addSocksServer(vmess, -1)
} else if (server.startsWith(EConfigType.TROJAN.protocolScheme)) {
val uri = URI(server)
vmess.address = uri.host
vmess.port = uri.port
vmess.id = uri.userInfo
vmess.remarks = uri.fragment ?: ""
vmess.subid = subid
if (!TextUtils.isEmpty((uri.rawQuery))) {
val queryParam = uri.rawQuery.split("&")
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
.toMap()
vmess.requestHost = queryParam["sni"] ?: ""
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
val uri = URI(str)
config = ServerConfig.create(EConfigType.TROJAN)
config.remarks = uri.fragment ?: ""
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.host
server.port = uri.port
server.password = uri.userInfo
}
addTrojanServer(vmess, -1)
} else if (server.startsWith(EConfigType.VLESS.protocolScheme)) {
val uri = URI(server)
vmess.address = uri.host
vmess.port = uri.port
vmess.id = uri.userInfo
vmess.remarks = uri.fragment ?: ""
vmess.subid = subid
val queryParam = uri.rawQuery.split("&")
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
.toMap()
//vmess.flow = queryParam["flow"] ?: ""
vmess.security = queryParam["encryption"] ?: "none"
vmess.streamSecurity = queryParam["security"] ?: ""
vmess.network = queryParam["type"] ?: "tcp"
when (vmess.network) {
"tcp" -> {
vmess.headerType = queryParam["headerType"] ?: "none"
vmess.requestHost = queryParam["host"] ?: ""
}
"h2",
"http" -> {
vmess.network = "h2"
vmess.requestHost = queryParam["host"] ?: ""
vmess.path = queryParam["path"] ?: "/"
}
"ws" -> {
vmess.requestHost = queryParam["host"] ?: ""
vmess.path = queryParam["path"] ?: "/"
}
"kcp" -> {
vmess.headerType = queryParam["headerType"] ?: "none"
vmess.path = queryParam["seed"] ?: ""
}
"quic" -> {
vmess.headerType = queryParam["headerType"] ?: "none"
vmess.requestHost = queryParam["quicSecurity"] ?: "none"
vmess.path = queryParam["key"] ?: ""
}
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, queryParam["sni"] ?: "")
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
val uri = URI(str)
val queryParam = uri.rawQuery.split("&")
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
.toMap()
config = ServerConfig.create(EConfigType.VLESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1
config.remarks = uri.fragment ?: ""
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.host
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow =queryParam["flow"] ?: ""
}
addVlessServer(vmess, -1)
} else {
val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"],
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"])
streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, sni)
}
if (config == null){
return R.string.toast_incorrect_protocol
}
config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null &&
vmess.subid.equals(removedSelectedServer.subid) &&
vmess.address.equals(removedSelectedServer.address) &&
vmess.port.equals(removedSelectedServer.port)) {
setActiveServer(configs.vmess.count() - 1)
config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() &&
config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
}
} catch (e: Exception) {
e.printStackTrace()
@@ -386,9 +237,9 @@ object AngConfigManager {
return 0
}
fun tryParseNewVmess(uri: String): AngConfig.VmessBean? {
private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean {
return runCatching {
val uri = URI(uri)
val uri = URI(uriString)
check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)")
@@ -398,52 +249,27 @@ object AngConfigManager {
val queryParam = uri.rawQuery.split("&")
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
.toMap()
val vmess = AngConfig.VmessBean()
vmess.address = uri.host
vmess.port = uri.port
vmess.id = uuid
vmess.alterId = alterId.toInt()
vmess.streamSecurity = if (tls) TLS else ""
vmess.remarks = uri.fragment
vmess.security = "auto"
// TODO: allowInsecure not supported
when (protocol) {
"tcp" -> {
vmess.network = "tcp"
vmess.headerType = queryParam["type"] ?: "none"
vmess.requestHost = queryParam["host"] ?: ""
}
"http" -> {
vmess.network = "h2"
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
}
"ws" -> {
vmess.network = "ws"
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
}
"kcp" -> {
vmess.network = "kcp"
vmess.headerType = queryParam["type"] ?: "none"
vmess.path = queryParam["seed"] ?: ""
}
"quic" -> {
vmess.network = "quic"
vmess.requestHost = queryParam["security"] ?: "none"
vmess.headerType = queryParam["type"] ?: "none"
vmess.path = queryParam["key"] ?: ""
}
val streamSetting = config.outboundBean?.streamSettings ?: return false
config.remarks = uri.fragment
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.host
vnext.port = uri.port
vnext.users[0].id = uuid
vnext.users[0].encryption = DEFAULT_SECURITY
vnext.users[0].alterId = alterId.toInt()
}
vmess
}.getOrNull()
val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"],
queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
queryParam["seed"], queryParam["security"], queryParam["key"])
streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni)
true
}.getOrElse { false }
}
private fun ResolveVmess4Kitsunebi(server: String): AngConfig.VmessBean {
val vmess = AngConfig.VmessBean()
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
val indexSplit = result.indexOf("?")
@@ -454,32 +280,29 @@ object AngConfigManager {
val arr1 = result.split('@')
if (arr1.count() != 2) {
return vmess
return false
}
val arr21 = arr1[0].split(':')
val arr22 = arr1[1].split(':')
if (arr21.count() != 2 || arr21.count() != 2) {
return vmess
if (arr21.count() != 2) {
return false
}
vmess.address = arr22[0]
vmess.port = Utils.parseInt(arr22[1])
vmess.security = arr21[0]
vmess.id = arr21[1]
vmess.security = "chacha20-poly1305"
vmess.network = "tcp"
vmess.headerType = "none"
vmess.remarks = "Alien"
vmess.alterId = 0
return vmess
config.remarks = "Alien"
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = arr22[0]
vnext.port = Utils.parseInt(arr22[1])
vnext.users[0].id = arr21[1]
vnext.users[0].encryption = arr21[0]
vnext.users[0].alterId = 0
}
return true
}
/**
* share config
*/
fun shareConfig(guid: String): String {
private fun shareConfig(guid: String): String {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
val outbound = config.getProxyOutbound() ?: return ""
@@ -632,7 +455,7 @@ object AngConfigManager {
continue
}
sb.append(url)
sb.appendln()
sb.appendLine()
}
if (sb.count() > 0) {
Utils.setClipboard(app.applicationContext, sb.toString())
@@ -653,8 +476,7 @@ object AngConfigManager {
if (TextUtils.isEmpty(conf)) {
return null
}
val bitmap = Utils.createQRCode(conf)
return bitmap
return Utils.createQRCode(conf)
} catch (e: Exception) {
e.printStackTrace()
@@ -681,87 +503,30 @@ object AngConfigManager {
return 0
}
/**
* import customize config
*/
fun importCustomizeConfig(server: String?): Int {
try {
if (server == null || TextUtils.isEmpty(server)) {
return R.string.toast_none_data
}
val config = ServerConfig.create(EConfigType.CUSTOM)
config.remarks = System.currentTimeMillis().toString()
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
MmkvManager.encodeServerConfig("", config)
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
/**
* getIndexViaGuid
*/
fun getIndexViaGuid(guid: String): Int {
try {
if (TextUtils.isEmpty(guid)) {
return -1
}
for (i in angConfig.vmess.indices) {
if (angConfig.vmess[i].guid == guid) {
return i
}
}
return -1
} catch (e: Exception) {
e.printStackTrace()
return -1
}
}
/**
* upgrade
*/
fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int {
private fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int {
try {
if (vmess.configVersion == 2) {
return 0
}
when (vmess.network) {
"kcp" -> {
}
"ws" -> {
"ws", "h2" -> {
var path = ""
var host = ""
val lstParameter = vmess.requestHost.split(";")
if (lstParameter.size > 0) {
path = lstParameter.get(0).trim()
if (lstParameter.isNotEmpty()) {
path = lstParameter[0].trim()
}
if (lstParameter.size > 1) {
path = lstParameter.get(0).trim()
host = lstParameter.get(1).trim()
path = lstParameter[0].trim()
host = lstParameter[1].trim()
}
vmess.path = path
vmess.requestHost = host
}
"h2" -> {
var path = ""
var host = ""
val lstParameter = vmess.requestHost.split(";")
if (lstParameter.size > 0) {
path = lstParameter.get(0).trim()
}
if (lstParameter.size > 1) {
path = lstParameter.get(0).trim()
host = lstParameter.get(1).trim()
}
vmess.path = path
vmess.requestHost = host
}
else -> {
}
}
vmess.configVersion = 2
return 0
@@ -771,62 +536,6 @@ object AngConfigManager {
}
}
fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = EConfigType.SHADOWSOCKS.value
vmess.address = vmess.address.trim()
vmess.id = vmess.id.trim()
vmess.security = vmess.security.trim()
if (index >= 0) {
//edit
angConfig.vmess[index] = vmess
} else {
//add
vmess.guid = System.currentTimeMillis().toString()
angConfig.vmess.add(vmess)
if (angConfig.vmess.count() == 1) {
angConfig.index = 0
}
}
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
fun addSocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = EConfigType.SOCKS.value
vmess.address = vmess.address.trim()
if (index >= 0) {
//edit
angConfig.vmess[index] = vmess
} else {
//add
vmess.guid = System.currentTimeMillis().toString()
angConfig.vmess.add(vmess)
if (angConfig.vmess.count() == 1) {
angConfig.index = 0
}
}
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
fun importBatchConfig(servers: String?, subid: String): Int {
try {
if (servers == null) {
@@ -834,8 +543,8 @@ object AngConfigManager {
}
val removedSelectedServer =
if (!TextUtils.isEmpty(subid)) {
configs.vmess.getOrNull(configs.index)?.let {
if (it.subid == subid) {
MmkvManager.decodeServerConfig(mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: "")?.let {
if (it.subscriptionId == subid) {
return@let it
}
return@let null
@@ -843,7 +552,7 @@ object AngConfigManager {
} else {
null
}
removeServerViaSubid(subid)
MmkvManager.removeServerViaSubid(subid)
// var servers = server
// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) {
@@ -866,7 +575,7 @@ object AngConfigManager {
}
fun saveSubItem(subItem: ArrayList<AngConfig.SubItemBean>): Int {
private fun saveSubItem(subItem: ArrayList<AngConfig.SubItemBean>): Int {
try {
if (subItem.count() <= 0) {
return -1
@@ -886,22 +595,6 @@ object AngConfigManager {
return 0
}
fun removeServerViaSubid(subid: String): Int {
if (TextUtils.isEmpty(subid) || configs.vmess.count() <= 0) {
return -1
}
for (k in configs.vmess.count() - 1 downTo 0) {
if (configs.vmess[k].subid.equals(subid)) {
angConfig.vmess.removeAt(k)
adjustIndexForRemovalAt(k)
}
}
storeConfigFile()
return 0
}
fun addSubItem(subItem: AngConfig.SubItemBean, index: Int): Int {
try {
if (index >= 0) {
@@ -942,7 +635,7 @@ object AngConfigManager {
return -1
}
val subid = angConfig.subItem[index].id
removeServerViaSubid(subid)
MmkvManager.removeServerViaSubid(subid)
angConfig.subItem.removeAt(index)
@@ -953,74 +646,4 @@ object AngConfigManager {
}
return 0
}
/**
* add or edit server
*/
fun addVlessServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = EConfigType.VLESS.value
vmess.address = vmess.address.trim()
vmess.id = vmess.id.trim()
vmess.alterId = 0
//vmess.flow = vmess.flow.trim()
vmess.security = vmess.security.trim()
vmess.network = vmess.network.trim()
vmess.headerType = vmess.headerType.trim()
vmess.requestHost = vmess.requestHost.trim()
vmess.path = vmess.path.trim()
vmess.streamSecurity = vmess.streamSecurity.trim()
if (index >= 0) {
//edit
angConfig.vmess[index] = vmess
} else {
//add
vmess.guid = Utils.getUuid()
angConfig.vmess.add(vmess)
if (angConfig.vmess.count() == 1) {
angConfig.index = 0
}
}
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
fun addTrojanServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = EConfigType.TROJAN.value
vmess.address = vmess.address.trim()
vmess.id = vmess.id.trim()
vmess.streamSecurity = TLS;
if (index >= 0) {
//edit
angConfig.vmess[index] = vmess
} else {
//add
vmess.guid = System.currentTimeMillis().toString()
angConfig.vmess.add(vmess)
if (angConfig.vmess.count() == 1) {
angConfig.index = 0
}
}
storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
}
return 0
}
}
@@ -14,6 +14,7 @@ object MmkvManager {
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) }
@@ -28,20 +29,49 @@ object MmkvManager {
return Gson().fromJson(json, ServerConfig::class.java)
}
fun encodeServerConfig(guid: String, config: ServerConfig) {
fun encodeServerConfig(guid: String, config: ServerConfig): String {
val key = if (guid.isBlank()) {
Utils.getUuid()
} else {
guid
}
serverStorage?.encode(key, Gson().toJson(config))
val serverList= Gson().fromJson(mainStorage?.decodeString(KEY_ANG_CONFIGS), Array<String>::class.java).toMutableList()
serverList.add(guid)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
}
return key
}
fun removeServer(guid: String) {
if (guid.isBlank()) {
return
}
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
mainStorage?.remove(KEY_SELECTED_SERVER)
}
val serverList= Gson().fromJson(mainStorage?.decodeString(KEY_ANG_CONFIGS), Array<String>::class.java).toMutableList()
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
@@ -19,6 +19,7 @@ import android.text.TextUtils
import android.util.Log
import android.util.Patterns
import android.webkit.URLUtil
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
@@ -35,6 +36,7 @@ import kotlin.coroutines.coroutineContext
object Utils {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val tcpTestingSockets = ArrayList<Socket?>()
/**
@@ -270,8 +272,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
}
@@ -279,26 +280,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
*/
@@ -398,8 +379,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
@@ -1,14 +1,15 @@
package com.v2ray.ang.util
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.google.gson.*
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.V2rayConfig
import org.json.JSONException
import org.json.JSONObject
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference
object V2rayConfigUtil {
// private val requestObj: JsonObject by lazy {
@@ -23,7 +24,7 @@ object V2rayConfigUtil {
/**
* 生成v2ray的客户端配置文件
*/
fun getV2rayConfig(app: AngApplication, guid: String): Result {
fun getV2rayConfig(context: Context, guid: String): Result {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
if (config.configType == EConfigType.CUSTOM) {
@@ -32,7 +33,7 @@ object V2rayConfigUtil {
return Result(true, customConfig)
}
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val result = getV2rayNonCustomConfig(app, outbound)
val result = getV2rayNonCustomConfig(context, outbound)
Log.d("V2rayConfigUtilGoLog", result.content)
return result
} catch (e: Exception) {
@@ -44,10 +45,10 @@ object V2rayConfigUtil {
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayNonCustomConfig(app: AngApplication, outbound: V2rayConfig.OutboundBean): Result {
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
val result = Result(false, "")
//取得默认配置
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
return result
}
@@ -55,18 +56,18 @@ object V2rayConfigUtil {
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
//v2rayConfig.log.loglevel = app.defaultDPreference.getPrefString(AppConfig.PREF_LOGLEVEL, "warning")
//v2rayConfig.log.loglevel = context.defaultDPreference.getPrefString(AppConfig.PREF_LOGLEVEL, "warning")
inbounds(v2rayConfig, app)
inbounds(v2rayConfig, context)
v2rayConfig.outbounds[0] = outbound
routing(v2rayConfig, app)
routing(v2rayConfig, context)
if (app.defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)) {
customLocalDns(v2rayConfig, app)
if (context.defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)) {
customLocalDns(v2rayConfig, context)
} else {
customRemoteDns(v2rayConfig, app)
customRemoteDns(v2rayConfig, context)
}
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
@@ -79,19 +80,19 @@ object V2rayConfigUtil {
/**
*
*/
private fun inbounds(v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun inbounds(v2rayConfig: V2rayConfig, context: Context): Boolean {
try {
//val socksPort = Utils.parseInt(app.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS))
//val httpPort = Utils.parseInt(app.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP))
//val socksPort = Utils.parseInt(context.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_SOCKS_PORT, AppConfig .PORT_SOCKS))
//val httpPort = Utils.parseInt(context.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP))
v2rayConfig.inbounds.forEach { curInbound ->
if (!app.defaultDPreference.getPrefBoolean(AppConfig.PREF_PROXY_SHARING, false)) {
if (!context.defaultDPreference.getPrefBoolean(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
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
v2rayConfig.inbounds[0].sniffing?.enabled = context.defaultDPreference.getPrefBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
//v2rayConfig.inbounds[1].port = httpPort
@@ -111,14 +112,14 @@ object V2rayConfigUtil {
/**
* routing
*/
private fun routing(v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun routing(v2rayConfig: V2rayConfig, context: Context): 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(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
routingUserRule(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
val routingMode = app.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_MODE, "0")
v2rayConfig.routing.domainStrategy = context.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
val routingMode = context.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_MODE, "0")
// Hardcode googleapis.cn
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
@@ -229,36 +230,36 @@ object V2rayConfigUtil {
/**
* Custom Dns
*/
private fun customLocalDns(v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun customLocalDns(v2rayConfig: V2rayConfig, context: Context): Boolean {
try {
val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>()
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
val remoteDns = Utils.getRemoteDnsServers(context.defaultDPreference)
remoteDns.forEach {
servers.add(it)
}
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
val domesticDns = Utils.getDomesticDnsServers(context.defaultDPreference)
val geositeCn = arrayListOf("geosite:cn")
val geoipCn = arrayListOf("geoip:cn")
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
val agDomain = userRule2Domian(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
if (agDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain, null))
}
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
val dirDomain = userRule2Domian(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
if (dirDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain, geoipCn))
}
val routingMode = app.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_MODE, "0")
val routingMode = context.defaultDPreference.getPrefString(AppConfig.PREF_ROUTING_MODE, "0")
if (routingMode == "2" || routingMode == "3") {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
}
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
val blkDomain = userRule2Domian(context.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
if (blkDomain.size > 0) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
}
@@ -278,7 +279,7 @@ object V2rayConfigUtil {
port = 53,
network = "tcp,udp")
//val localDnsPort = Utils.parseInt(app.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS))
//val localDnsPort = Utils.parseInt(context.defaultDPreference.getPrefStringNonEmpty(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS))
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
@@ -338,12 +339,12 @@ object V2rayConfigUtil {
/**
* Custom Remote Dns
*/
private fun customRemoteDns(v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun customRemoteDns(v2rayConfig: V2rayConfig, context: Context): Boolean {
try {
val servers = ArrayList<Any>()
val hosts = mutableMapOf<String, String>()
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
Utils.getRemoteDnsServers(context.defaultDPreference).forEach {
servers.add(it)
}
// hardcode googleapi rule to fix play store problems
@@ -13,16 +13,21 @@ 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.*
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) }
private val serverAffStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
val serverList by lazy { Gson().fromJson(mainStorage?.decodeString(KEY_ANG_CONFIGS), Array<String>::class.java).toList() }
var serverList= Gson().fromJson(mainStorage?.decodeString(KEY_ANG_CONFIGS), Array<String>::class.java).toMutableList()
private set
val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() }
val updateTestResultAction by lazy { MutableLiveData<String>() }
@@ -43,8 +48,26 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
super.onCleared()
}
fun getGuid(index: Int): String? {
return serverList.getOrNull(index)
fun reloadServerList() {
serverList = Gson().fromJson(mainStorage?.decodeString(KEY_ANG_CONFIGS), Array<String>::class.java).toMutableList()
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() {