Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1672494ee9 | |||
| bbbbc72d22 | |||
| 1e7f49b756 | |||
| ac4c0f7ee1 | |||
| 6cc91b1a89 | |||
| 45facff41d | |||
| ee703e6c95 | |||
| 87213c34a6 | |||
| 73a7c76183 | |||
| ed5282f2b3 | |||
| 390c657047 | |||
| 7071072862 | |||
| d111328541 | |||
| 76cb2aaf46 | |||
| 7ff1397163 |
@@ -13,4 +13,4 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Validate Fastlane Supply Metadata
|
||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.0.0
|
||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.1.0
|
||||
|
||||
+1
-1
Submodule AndroidLibXrayLite updated: c8a6ca7c5e...2715a3b110
@@ -21,7 +21,7 @@ A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-cor
|
||||
#### Geoip and Geosite
|
||||
- geoip.dat and geosite.dat files are in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
||||
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (Note it need a working proxy)
|
||||
- latest official [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip) can be imported manually
|
||||
- latest official [domain list](https://github.com/Loyalsoldier/v2ray-rules-dat) and [ip list](https://github.com/Loyalsoldier/geoip) can be imported manually
|
||||
- possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
||||
|
||||
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 643
|
||||
versionName = "1.9.43"
|
||||
versionCode = 645
|
||||
versionName = "1.9.45"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
|
||||
@@ -20,13 +20,6 @@
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
|
||||
@@ -13,13 +13,6 @@
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "阻断广告",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "绕过局域网IP",
|
||||
"outboundTag": "direct",
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
"port": "443",
|
||||
"network": "udp"
|
||||
},
|
||||
{
|
||||
"remarks": "Block ads and trackers",
|
||||
"outboundTag": "block",
|
||||
"domain": [
|
||||
"geosite:category-ads-all"
|
||||
]
|
||||
},
|
||||
{
|
||||
"remarks": "Direct LAN IP",
|
||||
"outboundTag": "direct",
|
||||
|
||||
@@ -5,6 +5,7 @@ object AppConfig {
|
||||
|
||||
/** The application's package name. */
|
||||
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
|
||||
const val TAG = BuildConfig.APPLICATION_ID
|
||||
|
||||
/** Directory names used in the app's file system. */
|
||||
const val DIR_ASSETS = "assets"
|
||||
|
||||
@@ -8,6 +8,7 @@ enum class Language(val code: String) {
|
||||
VIETNAMESE("vi"),
|
||||
RUSSIAN("ru"),
|
||||
PERSIAN("fa"),
|
||||
ARABIC("ar"),
|
||||
BANGLA("bn"),
|
||||
BAKHTIARI("bqi-rIR");
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ data class V2rayConfig(
|
||||
var policy: PolicyBean?,
|
||||
val inbounds: ArrayList<InboundBean>,
|
||||
var outbounds: ArrayList<OutboundBean>,
|
||||
var dns: DnsBean,
|
||||
var dns: DnsBean? = null,
|
||||
val routing: RoutingBean,
|
||||
val api: Any? = null,
|
||||
val transport: Any? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.NetworkType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
@@ -82,7 +84,7 @@ object ShadowsocksFmt : FmtBase() {
|
||||
config.remarks =
|
||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to decode remarks in SS legacy URL", e)
|
||||
}
|
||||
|
||||
result = result.substring(0, indexSplit)
|
||||
|
||||
@@ -33,7 +33,7 @@ object VmessFmt : FmtBase() {
|
||||
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
||||
result = Utils.decode(result)
|
||||
if (TextUtils.isEmpty(result)) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
|
||||
Log.w(AppConfig.TAG, "Toast decoding failed")
|
||||
return null
|
||||
}
|
||||
val vmessQRCode = JsonUtil.fromJson(result, VmessQRCode::class.java)
|
||||
@@ -43,7 +43,7 @@ object VmessFmt : FmtBase() {
|
||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||
) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||
Log.w(AppConfig.TAG, "Toast incorrect protocol")
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ object AngConfigManager {
|
||||
Utils.setClipboard(context, conf)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to share config to clipboard", e)
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
@@ -71,7 +71,7 @@ object AngConfigManager {
|
||||
}
|
||||
return sb.lines().count()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to share non-custom configs to clipboard", e)
|
||||
return -1
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ object AngConfigManager {
|
||||
return QRCodeDecoder.createQRCode(conf)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to share config as QR code", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ object AngConfigManager {
|
||||
return -1
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to share full content to clipboard", e)
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
@@ -148,7 +148,7 @@ object AngConfigManager {
|
||||
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to share config for GUID: $guid", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ object AngConfigManager {
|
||||
}
|
||||
return count
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse batch subscription", e)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -251,7 +251,7 @@ object AngConfigManager {
|
||||
}
|
||||
return count
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse batch config", e)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -287,7 +287,7 @@ object AngConfigManager {
|
||||
return count
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse custom config server JSON array", e)
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -298,7 +298,7 @@ object AngConfigManager {
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
return 1
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse custom config server as single config", e)
|
||||
}
|
||||
return 0
|
||||
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
|
||||
@@ -308,7 +308,7 @@ object AngConfigManager {
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
return 1
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse WireGuard config file", e)
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
@@ -372,7 +372,7 @@ object AngConfigManager {
|
||||
MmkvManager.setSelectServer(guid)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse config", e)
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
@@ -390,7 +390,7 @@ object AngConfigManager {
|
||||
count += updateConfigViaSub(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to update config via all subscriptions", e)
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
@@ -417,21 +417,20 @@ object AngConfigManager {
|
||||
if (!Utils.isValidUrl(url)) {
|
||||
return 0
|
||||
}
|
||||
Log.d(AppConfig.ANG_PACKAGE, url)
|
||||
Log.i(AppConfig.TAG, url)
|
||||
|
||||
var configText = try {
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
HttpUtil.getUrlContentWithUserAgent(url, 15000, httpPort)
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error, try……")
|
||||
//e.printStackTrace()
|
||||
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
|
||||
""
|
||||
}
|
||||
if (configText.isEmpty()) {
|
||||
configText = try {
|
||||
HttpUtil.getUrlContentWithUserAgent(url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get URL content with user agent", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -440,7 +439,7 @@ object AngConfigManager {
|
||||
}
|
||||
return parseConfigViaSub(configText, it.first, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to update config via subscription", e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.v2ray.ang.handler
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.NetworkType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
@@ -26,7 +25,7 @@ object MigrateManager {
|
||||
return false
|
||||
}
|
||||
val serverList = serverStorage.allKeys() ?: return false
|
||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + serverList.count())
|
||||
Log.i(AppConfig.TAG, "migrateServerConfig2Profile-" + serverList.count())
|
||||
|
||||
for (guid in serverList) {
|
||||
var configOld = decodeServerConfigOld(guid) ?: continue
|
||||
@@ -43,9 +42,9 @@ object MigrateManager {
|
||||
//check and remove old
|
||||
decodeServerConfig(guid) ?: continue
|
||||
serverStorage.remove(guid)
|
||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-" + config.remarks)
|
||||
Log.i(AppConfig.TAG, "migrateServerConfig2Profile-" + config.remarks)
|
||||
}
|
||||
Log.d(ANG_PACKAGE, "migrateServerConfig2Profile-end")
|
||||
Log.i(AppConfig.TAG, "migrateServerConfig2Profile-end")
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -571,7 +571,7 @@ object MmkvManager {
|
||||
* @param startOnBoot Whether to start on boot.
|
||||
*/
|
||||
fun encodeStartOnBoot(startOnBoot: Boolean) {
|
||||
MmkvManager.encodeSettings(PREF_IS_BOOTED, startOnBoot)
|
||||
encodeSettings(PREF_IS_BOOTED, startOnBoot)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,7 +84,7 @@ object SettingsManager {
|
||||
resetRoutingRulesetsCommon(rulesetList)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(ANG_PACKAGE, "Failed to reset routing rulesets", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -167,11 +167,11 @@ object SettingsManager {
|
||||
}
|
||||
|
||||
val guid = MmkvManager.getSelectServer() ?: return false
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return false
|
||||
val config = decodeServerConfig(guid) ?: return false
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val raw = MmkvManager.decodeServerRaw(guid) ?: return false
|
||||
val v2rayConfig = JsonUtil.fromJson(raw, V2rayConfig::class.java)
|
||||
val exist = v2rayConfig.routing.rules.filter { it.outboundTag == TAG_DIRECT }?.any {
|
||||
val exist = v2rayConfig.routing.rules.filter { it.outboundTag == TAG_DIRECT }.any {
|
||||
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
|
||||
}
|
||||
return exist == true
|
||||
@@ -265,10 +265,7 @@ object SettingsManager {
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
Log.i(
|
||||
ANG_PACKAGE,
|
||||
"Copied from apk assets folder to ${target.absolutePath}"
|
||||
)
|
||||
Log.i(AppConfig.TAG, "Copied from apk assets folder to ${target.absolutePath}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
||||
@@ -343,6 +340,7 @@ object SettingsManager {
|
||||
Language.VIETNAMESE -> Locale("vi")
|
||||
Language.RUSSIAN -> Locale("ru")
|
||||
Language.PERSIAN -> Locale("fa")
|
||||
Language.ARABIC -> Locale("ar")
|
||||
Language.BANGLA -> Locale("bn")
|
||||
Language.BAKHTIARI -> Locale("bqi", "IR")
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ object SpeedtestManager {
|
||||
return try {
|
||||
Libv2ray.measureOutboundDelay(config, SettingsManager.getDelayTestUrl())
|
||||
} catch (e: Exception) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
||||
Log.e(AppConfig.TAG, "Failed to measure outbound delay", e)
|
||||
-1L
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ object SpeedtestManager {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to ping URL: $url", e)
|
||||
}
|
||||
return "-1ms"
|
||||
}
|
||||
@@ -103,11 +103,11 @@ object SpeedtestManager {
|
||||
socket.close()
|
||||
return time
|
||||
} catch (e: UnknownHostException) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Unknown host: $url", e)
|
||||
} catch (e: IOException) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
|
||||
Log.e(AppConfig.TAG, "socketConnectTime IOException: $e")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to establish socket connection to $url:$port", e)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -152,13 +152,13 @@ object SpeedtestManager {
|
||||
)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
|
||||
Log.e(AppConfig.TAG, "Connection test IOException", e)
|
||||
result = context.getString(R.string.connection_test_error, e.message)
|
||||
} catch (e: Exception) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "testConnection Exception: " + Log.getStackTraceString(e))
|
||||
Log.e(AppConfig.TAG, "Connection test Exception", e)
|
||||
result = context.getString(R.string.connection_test_error, e.message)
|
||||
} finally {
|
||||
conn?.disconnect()
|
||||
conn.disconnect()
|
||||
}
|
||||
|
||||
return Pair(elapsed, result)
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.DEFAULT_NETWORK
|
||||
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
|
||||
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
|
||||
@@ -52,6 +51,7 @@ import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
object V2rayConfigManager {
|
||||
private var initConfigCache: String? = null
|
||||
|
||||
/**
|
||||
* Retrieves the V2ray configuration for the given GUID.
|
||||
@@ -63,47 +63,72 @@ object V2rayConfigManager {
|
||||
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
|
||||
try {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
|
||||
val domainPort = config.getServerAddressAndPort()
|
||||
return ConfigResult(true, guid, raw, domainPort)
|
||||
return if (config.configType == EConfigType.CUSTOM) {
|
||||
getV2rayCustomConfig(guid, config)
|
||||
} else {
|
||||
getV2rayNormalConfig(context, guid, config)
|
||||
}
|
||||
|
||||
val result = getV2rayNonCustomConfig(context, config)
|
||||
//Log.d(ANG_PACKAGE, result.content)
|
||||
result.guid = guid
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get V2ray config", e)
|
||||
return ConfigResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the non-custom V2ray configuration.
|
||||
* Retrieves the speedtest V2ray configuration for the given GUID.
|
||||
*
|
||||
* @param context The context in which the function is called.
|
||||
* @param context The context of the caller.
|
||||
* @param guid The unique identifier for the V2ray configuration.
|
||||
* @return A ConfigResult object containing the configuration details or indicating failure.
|
||||
*/
|
||||
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
|
||||
try {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
||||
return if (config.configType == EConfigType.CUSTOM) {
|
||||
getV2rayCustomConfig(guid, config)
|
||||
} else {
|
||||
getV2rayNormalConfig4Speedtest(context, guid, config)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e)
|
||||
return ConfigResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the custom V2ray configuration.
|
||||
*
|
||||
* @param guid The unique identifier for the V2ray configuration.
|
||||
* @param config The profile item containing the configuration details.
|
||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
||||
*/
|
||||
private fun getV2rayNonCustomConfig(context: Context, config: ProfileItem): ConfigResult {
|
||||
private fun getV2rayCustomConfig(guid: String, config: ProfileItem): ConfigResult {
|
||||
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
|
||||
val domainPort = config.getServerAddressAndPort()
|
||||
return ConfigResult(true, guid, raw, domainPort)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the normal V2ray configuration.
|
||||
*
|
||||
* @param context The context in which the function is called.
|
||||
* @param guid The unique identifier for the V2ray configuration.
|
||||
* @param config The profile item containing the configuration details.
|
||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
||||
*/
|
||||
private fun getV2rayNormalConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
||||
val result = ConfigResult(false)
|
||||
|
||||
val address = config.server ?: return result
|
||||
if (!Utils.isIpAddress(address)) {
|
||||
if (!Utils.isValidUrl(address)) {
|
||||
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
|
||||
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
v2rayConfig.log.loglevel =
|
||||
MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
val v2rayConfig = initV2rayConfig(context) ?: return result
|
||||
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
v2rayConfig.remarks = config.remarks
|
||||
|
||||
inbounds(v2rayConfig)
|
||||
@@ -129,9 +154,75 @@ object V2rayConfigManager {
|
||||
result.status = true
|
||||
result.content = v2rayConfig.toPrettyPrinting()
|
||||
result.domainPort = if (retMore.first) retMore.second else retOut.second
|
||||
result.guid = guid
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the normal V2ray configuration for speedtest.
|
||||
*
|
||||
* @param context The context in which the function is called.
|
||||
* @param guid The unique identifier for the V2ray configuration.
|
||||
* @param config The profile item containing the configuration details.
|
||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
||||
*/
|
||||
private fun getV2rayNormalConfig4Speedtest(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
||||
val result = ConfigResult(false)
|
||||
|
||||
val address = config.server ?: return result
|
||||
if (!Utils.isIpAddress(address)) {
|
||||
if (!Utils.isValidUrl(address)) {
|
||||
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val v2rayConfig = initV2rayConfig(context) ?: return result
|
||||
|
||||
val isPlugin = config.configType == EConfigType.HYSTERIA2
|
||||
val retOut = outbounds(v2rayConfig, config, isPlugin) ?: return result
|
||||
val retMore = moreOutbounds(v2rayConfig, config.subscriptionId, isPlugin)
|
||||
|
||||
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
v2rayConfig.inbounds.clear()
|
||||
v2rayConfig.routing.rules.clear()
|
||||
v2rayConfig.dns = null
|
||||
v2rayConfig.fakedns = null
|
||||
v2rayConfig.stats = null
|
||||
v2rayConfig.policy = null
|
||||
|
||||
v2rayConfig.outbounds.forEach { key ->
|
||||
key.mux = null
|
||||
}
|
||||
|
||||
result.status = true
|
||||
result.content = v2rayConfig.toPrettyPrinting()
|
||||
result.domainPort = if (retMore.first) retMore.second else retOut.second
|
||||
result.guid = guid
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes V2ray configuration.
|
||||
*
|
||||
* This function loads the V2ray configuration from assets or from a cached value.
|
||||
* It first attempts to use the cached configuration if available, otherwise reads
|
||||
* the configuration from the "v2ray_config.json" asset file.
|
||||
*
|
||||
* @param context Android context used to access application assets
|
||||
* @return V2rayConfig object parsed from the JSON configuration, or null if the configuration is empty
|
||||
*/
|
||||
|
||||
private fun initV2rayConfig(context: Context): V2rayConfig? {
|
||||
val assets = initConfigCache ?: Utils.readTextFromAssets(context, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return null
|
||||
}
|
||||
initConfigCache = assets
|
||||
val config = JsonUtil.fromJson(assets, V2rayConfig::class.java)
|
||||
return config
|
||||
}
|
||||
|
||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
@@ -164,7 +255,7 @@ object V2rayConfigManager {
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure inbounds", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -227,7 +318,7 @@ object V2rayConfigManager {
|
||||
routingUserRule(key, v2rayConfig)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure routing", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -244,7 +335,7 @@ object V2rayConfigManager {
|
||||
v2rayConfig.routing.rules.add(rule)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to apply routing user rule", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +365,7 @@ object V2rayConfigManager {
|
||||
val proxyDomain = userRule2Domain(TAG_PROXY)
|
||||
val directDomain = userRule2Domain(TAG_DIRECT)
|
||||
// fakedns with all domains to make it always top priority
|
||||
v2rayConfig.dns.servers?.add(
|
||||
v2rayConfig.dns?.servers?.add(
|
||||
0,
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
address = "fakedns",
|
||||
@@ -330,7 +421,7 @@ object V2rayConfigManager {
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure custom local DNS", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -394,7 +485,7 @@ object V2rayConfigManager {
|
||||
if (userHostsMap != null) hosts.putAll(userHostsMap)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure user DNS hosts", e)
|
||||
}
|
||||
|
||||
//block dns
|
||||
@@ -433,7 +524,7 @@ object V2rayConfigManager {
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure DNS", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -504,7 +595,7 @@ object V2rayConfigManager {
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to update outbound with global settings", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -570,7 +661,7 @@ object V2rayConfigManager {
|
||||
dialerProxy = TAG_FRAGMENT
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to update outbound fragment", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -633,7 +724,7 @@ object V2rayConfigManager {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to configure more outbounds", e)
|
||||
return returnPair
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class TaskerReceiver : BroadcastReceiver() {
|
||||
V2RayServiceManager.stopVService(context)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
android.util.Log.e(AppConfig.TAG, "Error processing Tasker broadcast", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.v2ray.ang.service
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -16,7 +16,7 @@ class ProcessService {
|
||||
* @param cmd The command to run.
|
||||
*/
|
||||
fun runProcess(context: Context, cmd: MutableList<String>) {
|
||||
Log.d(ANG_PACKAGE, cmd.toString())
|
||||
Log.i(AppConfig.TAG, cmd.toString())
|
||||
|
||||
try {
|
||||
val proBuilder = ProcessBuilder(cmd)
|
||||
@@ -27,14 +27,14 @@ class ProcessService {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Thread.sleep(50L)
|
||||
Log.d(ANG_PACKAGE, "runProcess check")
|
||||
Log.i(AppConfig.TAG, "runProcess check")
|
||||
process?.waitFor()
|
||||
Log.d(ANG_PACKAGE, "runProcess exited")
|
||||
Log.i(AppConfig.TAG, "runProcess exited")
|
||||
}
|
||||
Log.d(ANG_PACKAGE, process.toString())
|
||||
Log.i(AppConfig.TAG, process.toString())
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, e.toString(), e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ class ProcessService {
|
||||
*/
|
||||
fun stopProcess() {
|
||||
try {
|
||||
Log.d(ANG_PACKAGE, "runProcess destroy")
|
||||
Log.i(AppConfig.TAG, "runProcess destroy")
|
||||
process?.destroy()
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to destroy process", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.v2ray.ang.AppConfig
|
||||
@@ -61,7 +62,7 @@ class QSTileService : TileService() {
|
||||
applicationContext.unregisterReceiver(mMsgReceive)
|
||||
mMsgReceive = null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to unregister receiver", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ object SubscriptionUpdater {
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun doWork(): Result {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "subscription automatic update starting")
|
||||
Log.i(AppConfig.TAG, "subscription automatic update starting")
|
||||
|
||||
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
|
||||
|
||||
@@ -56,10 +56,7 @@ object SubscriptionUpdater {
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
notificationManager.notify(3, notification.build())
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"subscription automatic update: ---${subItem.remarks}"
|
||||
)
|
||||
Log.i(AppConfig.TAG, "subscription automatic update: ---${subItem.remarks}")
|
||||
updateConfigViaSub(Pair(sub.first, subItem))
|
||||
notification.setContentText("Updating ${subItem.remarks}")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
@@ -144,7 +143,7 @@ object V2RayServiceManager {
|
||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to register broadcast receiver", e)
|
||||
}
|
||||
|
||||
v2rayPoint.configureFileContent = result.content
|
||||
@@ -154,7 +153,7 @@ object V2RayServiceManager {
|
||||
try {
|
||||
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to start V2Ray loop", e)
|
||||
}
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
@@ -179,7 +178,7 @@ object V2RayServiceManager {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to stop V2Ray loop", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +189,7 @@ object V2RayServiceManager {
|
||||
try {
|
||||
service.unregisterReceiver(mMsgReceive)
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
|
||||
}
|
||||
PluginUtil.stopPlugin()
|
||||
}
|
||||
@@ -217,14 +216,14 @@ object V2RayServiceManager {
|
||||
try {
|
||||
time = v2rayPoint.measureDelay(SettingsManager.getDelayTestUrl())
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||
Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
|
||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||
}
|
||||
if (time == -1L) {
|
||||
try {
|
||||
time = v2rayPoint.measureDelay(SettingsManager.getDelayTestUrl(true))
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||
Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
|
||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||
}
|
||||
}
|
||||
@@ -255,7 +254,7 @@ object V2RayServiceManager {
|
||||
serviceControl.stopService()
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to stop service in callback", e)
|
||||
-1
|
||||
}
|
||||
}
|
||||
@@ -291,7 +290,7 @@ object V2RayServiceManager {
|
||||
NotificationService.startSpeedNotification(currentConfig)
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to setup service in callback", e)
|
||||
-1
|
||||
}
|
||||
}
|
||||
@@ -323,12 +322,12 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_STOP -> {
|
||||
Log.d(ANG_PACKAGE, "Stop Service")
|
||||
Log.i(AppConfig.TAG, "Stop Service")
|
||||
serviceControl.stopService()
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_RESTART -> {
|
||||
Log.d(ANG_PACKAGE, "Restart Service")
|
||||
Log.i(AppConfig.TAG, "Restart Service")
|
||||
serviceControl.stopService()
|
||||
Thread.sleep(500L)
|
||||
startVService(serviceControl.getService())
|
||||
@@ -341,12 +340,12 @@ object V2RayServiceManager {
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats")
|
||||
NotificationService.stopSpeedNotification(currentConfig)
|
||||
}
|
||||
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats")
|
||||
NotificationService.startSpeedNotification(currentConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ class V2RayTestService : Service() {
|
||||
val delay = PluginUtil.realPingHy2(this, config)
|
||||
return delay
|
||||
} else {
|
||||
val config = V2rayConfigManager.getV2rayConfig(this, guid)
|
||||
if (!config.status) {
|
||||
val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(this, guid)
|
||||
if (!configResult.status) {
|
||||
return retFailure
|
||||
}
|
||||
return SpeedtestManager.realPing(config.content)
|
||||
return SpeedtestManager.realPing(configResult.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.os.StrictMode
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
@@ -40,6 +39,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private const val PRIVATE_VLAN6_CLIENT = "fc00::10:10:14:1"
|
||||
private const val PRIVATE_VLAN6_ROUTER = "fc00::10:10:14:2"
|
||||
private const val TUN2SOCKS = "libtun2socks.so"
|
||||
|
||||
}
|
||||
|
||||
private lateinit var mInterface: ParcelFileDescriptor
|
||||
@@ -209,7 +209,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
else
|
||||
builder.addAllowedApplication(it)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.d(ANG_PACKAGE, "setup error : --${e.localizedMessage}")
|
||||
Log.e(AppConfig.TAG, "Failed to configure app in VPN: ${e.localizedMessage}", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -227,7 +227,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
try {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to request default network", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
// non-nullable lateinit var
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e)
|
||||
stopV2Ray()
|
||||
}
|
||||
return false
|
||||
@@ -277,7 +277,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
cmd.add("--dnsgw")
|
||||
cmd.add("$LOOPBACK:${localDnsPort}")
|
||||
}
|
||||
Log.d(packageName, cmd.toString())
|
||||
Log.i(AppConfig.TAG, cmd.toString())
|
||||
|
||||
try {
|
||||
val proBuilder = ProcessBuilder(cmd)
|
||||
@@ -286,19 +286,19 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
.directory(applicationContext.filesDir)
|
||||
.start()
|
||||
Thread {
|
||||
Log.d(packageName, "$TUN2SOCKS check")
|
||||
Log.i(AppConfig.TAG, "$TUN2SOCKS check")
|
||||
process.waitFor()
|
||||
Log.d(packageName, "$TUN2SOCKS exited")
|
||||
Log.i(AppConfig.TAG, "$TUN2SOCKS exited")
|
||||
if (isRunning) {
|
||||
Log.d(packageName, "$TUN2SOCKS restart")
|
||||
Log.i(AppConfig.TAG, "$TUN2SOCKS restart")
|
||||
runTun2socks()
|
||||
}
|
||||
}.start()
|
||||
Log.d(packageName, process.toString())
|
||||
Log.i(AppConfig.TAG, process.toString())
|
||||
|
||||
sendFd()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to start tun2socks process", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,13 +309,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private fun sendFd() {
|
||||
val fd = mInterface.fileDescriptor
|
||||
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
||||
Log.d(packageName, path)
|
||||
Log.i(AppConfig.TAG, path)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var tries = 0
|
||||
while (true) try {
|
||||
Thread.sleep(50L shl tries)
|
||||
Log.d(packageName, "sendFd tries: $tries")
|
||||
Log.i(AppConfig.TAG, "sendFd tries: $tries")
|
||||
LocalSocket().use { localSocket ->
|
||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
||||
@@ -323,7 +323,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e)
|
||||
if (tries > 5) break
|
||||
tries += 1
|
||||
}
|
||||
@@ -349,10 +349,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
try {
|
||||
Log.d(packageName, "tun2socks destroy")
|
||||
Log.i(AppConfig.TAG, "tun2socks destroy")
|
||||
process.destroy()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to destroy tun2socks process", e)
|
||||
}
|
||||
|
||||
V2RayServiceManager.stopV2rayPoint()
|
||||
@@ -367,8 +367,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
|
||||
try {
|
||||
mInterface.close()
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to close VPN interface", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class AboutActivity : BaseActivity() {
|
||||
try {
|
||||
showFileChooser()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to show file chooser", e)
|
||||
}
|
||||
} else {
|
||||
toast(R.string.toast_permission_denied)
|
||||
@@ -90,7 +90,7 @@ class AboutActivity : BaseActivity() {
|
||||
try {
|
||||
showFileChooser()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to show file chooser", e)
|
||||
}
|
||||
} else {
|
||||
requestPermissionLauncher.launch(permission)
|
||||
@@ -106,7 +106,7 @@ class AboutActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
binding.layoutOssLicenses.setOnClickListener {
|
||||
val webView = android.webkit.WebView(this);
|
||||
val webView = android.webkit.WebView(this)
|
||||
webView.loadUrl("file:///android_asset/open_source_licenses.html")
|
||||
android.app.AlertDialog.Builder(this)
|
||||
.setTitle("Open source licenses")
|
||||
@@ -169,7 +169,7 @@ class AboutActivity : BaseActivity() {
|
||||
try {
|
||||
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
||||
} catch (ex: android.content.ActivityNotFoundException) {
|
||||
Log.e(AppConfig.ANG_PACKAGE, "File chooser activity not found: ${ex.message}", ex)
|
||||
Log.e(AppConfig.TAG, "File chooser activity not found", ex)
|
||||
toast(R.string.toast_require_file_manager)
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ class AboutActivity : BaseActivity() {
|
||||
toastError(R.string.toast_failure)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.ANG_PACKAGE, "Error during file restore: ${e.message}", e)
|
||||
Log.e(AppConfig.TAG, "Error during file restore", e)
|
||||
toastError(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||
@@ -68,7 +70,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get logcat", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +91,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to clear logcat", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.databinding.ItemRecyclerLogcatBinding
|
||||
|
||||
class LogcatRecyclerAdapter(val activity: LogcatActivity) : RecyclerView.Adapter<LogcatRecyclerAdapter.MainViewHolder>() {
|
||||
private var mActivity: LogcatActivity = activity
|
||||
|
||||
|
||||
override fun getItemCount() = mActivity.logsets.size
|
||||
|
||||
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
|
||||
@@ -22,7 +25,7 @@ class LogcatRecyclerAdapter(val activity: LogcatActivity) : RecyclerView.Adapter
|
||||
holder.itemSubSettingBinding.logContent.text = if (content.count() > 1) content.last().trim() else ""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Error binding log view data", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -485,7 +486,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
val clipboard = Utils.getClipboard(this)
|
||||
importBatchConfig(clipboard)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to import config from clipboard", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -515,16 +516,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
toastError(R.string.toast_failure)
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to import batch config", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* import config from local config file
|
||||
*/
|
||||
private fun importConfigLocal(): Boolean {
|
||||
try {
|
||||
showFileChooser()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to import config from local file", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -744,7 +748,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
importBatchConfig(input?.bufferedReader()?.readText())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to read content from URI", e)
|
||||
}
|
||||
} else {
|
||||
requestPermissionLauncher.launch(permission)
|
||||
@@ -807,6 +811,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
Intent(this, SettingsActivity::class.java)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value == true)
|
||||
)
|
||||
|
||||
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
|
||||
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.ui
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -187,7 +188,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Error when sharing server", e)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
@@ -299,7 +300,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
delay(500)
|
||||
V2RayServiceManager.startVService(mActivity)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to restart V2Ray service", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
ActivityBypassListBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
|
||||
private var adapter: PerAppProxyAdapter? = null
|
||||
private var appsAll: List<AppInfo>? = null
|
||||
|
||||
@@ -164,7 +165,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
content = HttpUtil.getUrlContent(url, 5000, httpPort) ?: ""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
Log.d(ANG_PACKAGE, content)
|
||||
Log.i(AppConfig.TAG, content)
|
||||
selectProxyApp(content, true)
|
||||
toastSuccess(R.string.toast_success)
|
||||
binding.pbWaiting.hide()
|
||||
@@ -205,7 +206,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
adapter?.let { it ->
|
||||
it.apps.forEach block@{
|
||||
val packageName = it.packageName
|
||||
Log.d(ANG_PACKAGE, packageName)
|
||||
Log.i(AppConfig.TAG, packageName)
|
||||
if (!inProxyApps(proxyApps, packageName, force)) {
|
||||
adapter?.blacklist?.add(packageName)
|
||||
println(packageName)
|
||||
@@ -218,7 +219,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
adapter?.let { it ->
|
||||
it.apps.forEach block@{
|
||||
val packageName = it.packageName
|
||||
Log.d(ANG_PACKAGE, packageName)
|
||||
Log.i(AppConfig.TAG, packageName)
|
||||
if (inProxyApps(proxyApps, packageName, force)) {
|
||||
adapter?.blacklist?.add(packageName)
|
||||
println(packageName)
|
||||
@@ -229,7 +230,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Error selecting proxy app", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -110,7 +111,7 @@ class RoutingSettingActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to import predefined ruleset", e)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
@@ -126,7 +127,7 @@ class RoutingSettingActivity : BaseActivity() {
|
||||
val clipboard = try {
|
||||
Utils.getClipboard(this)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get clipboard content", e)
|
||||
toastError(R.string.toast_failure)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -21,6 +22,7 @@ import io.github.g00fy2.quickie.config.ScannerConfig
|
||||
|
||||
class ScannerActivity : BaseActivity() {
|
||||
|
||||
|
||||
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
||||
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val uri = it.data?.data
|
||||
@@ -37,7 +39,7 @@ class ScannerActivity : BaseActivity() {
|
||||
finished(text)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to decode QR code from file", e)
|
||||
toast(R.string.toast_decoding_failed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.DEFAULT_PORT
|
||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||
import com.v2ray.ang.AppConfig.REALITY
|
||||
@@ -481,7 +480,7 @@ class ServerActivity : BaseActivity() {
|
||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
config.subscriptionId = subscriptionId.orEmpty()
|
||||
}
|
||||
Log.d(ANG_PACKAGE, JsonUtil.toJsonPretty(config) ?: "")
|
||||
//Log.i(AppConfig.TAG, JsonUtil.toJsonPretty(config) ?: "")
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
toastSuccess(R.string.toast_success)
|
||||
finish()
|
||||
|
||||
@@ -2,11 +2,13 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
@@ -76,7 +78,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
val profileItem = try {
|
||||
CustomFmt.parse(binding.editor.text.toString())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse custom configuration", e)
|
||||
toast("${getString(R.string.toast_malformed_josn)} ${e.cause?.message}")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package com.v2ray.ang.ui
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||
@@ -81,7 +83,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Share subscription failed", e)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.ui
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -60,7 +61,7 @@ class TaskerActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to initialize Tasker settings", e)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
@@ -54,7 +55,7 @@ class UrlSchemeActivity : BaseActivity() {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Error processing URL scheme", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ class UrlSchemeActivity : BaseActivity() {
|
||||
if (uriString.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
Log.d("UrlScheme", uriString)
|
||||
Log.i(AppConfig.TAG, uriString)
|
||||
|
||||
var decodedUrl = URLDecoder.decode(uriString, "UTF-8")
|
||||
val uri = Uri.parse(decodedUrl)
|
||||
@@ -70,7 +71,7 @@ class UrlSchemeActivity : BaseActivity() {
|
||||
if (uri.fragment.isNullOrEmpty() && !fragment.isNullOrEmpty()) {
|
||||
decodedUrl += "#${fragment}"
|
||||
}
|
||||
Log.d("UrlScheme-decodedUrl", decodedUrl)
|
||||
Log.i(AppConfig.TAG, decodedUrl)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val (count, countSub) = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.Date
|
||||
class UserAssetActivity : BaseActivity() {
|
||||
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||
|
||||
|
||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
|
||||
|
||||
@@ -163,7 +164,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
}.also { cursor.close() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get cursor name", e)
|
||||
null
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
.putExtra(UserAssetUrlActivity.ASSET_URL_QRCODE, url)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to import asset from URL", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -207,12 +208,16 @@ class UserAssetActivity : BaseActivity() {
|
||||
var resultCount = 0
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
assets.forEach {
|
||||
var result = downloadGeo(it.second, 15000, httpPort)
|
||||
if (!result) {
|
||||
result = downloadGeo(it.second, 15000, 0)
|
||||
try {
|
||||
var result = downloadGeo(it.second, 15000, httpPort)
|
||||
if (!result) {
|
||||
result = downloadGeo(it.second, 15000, 0)
|
||||
}
|
||||
if (result)
|
||||
resultCount++
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to download geo file: ${it.second.remarks}", e)
|
||||
}
|
||||
if (result)
|
||||
resultCount++
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (resultCount > 0) {
|
||||
@@ -229,7 +234,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
private fun downloadGeo(item: AssetUrlItem, timeout: Int, httpPort: Int): Boolean {
|
||||
val targetTemp = File(extDir, item.remarks + "_temp")
|
||||
val target = File(extDir, item.remarks)
|
||||
//Log.d(AppConfig.ANG_PACKAGE, url)
|
||||
Log.i(AppConfig.TAG, "Downloading geo file: ${item.remarks} from ${item.url}")
|
||||
|
||||
val conn = HttpUtil.createProxyConnection(item.url, httpPort, timeout, timeout, needStream = true) ?: return false
|
||||
try {
|
||||
@@ -244,10 +249,10 @@ class UserAssetActivity : BaseActivity() {
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.ANG_PACKAGE, Log.getStackTraceString(e))
|
||||
Log.e(AppConfig.TAG, "Failed to download geo file: ${item.remarks}", e)
|
||||
return false
|
||||
} finally {
|
||||
conn?.disconnect()
|
||||
conn.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
@@ -75,7 +77,11 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
// remove file associated with the asset
|
||||
val file = extDir.resolve(assetItem.remarks)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
try {
|
||||
file.delete()
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to delete asset file: ${file.path}", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assetId = Utils.getUuid()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.util.Utils.encode
|
||||
@@ -10,6 +12,7 @@ import java.util.*
|
||||
|
||||
object HttpUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Converts a URL string to its ASCII representation.
|
||||
*
|
||||
@@ -142,7 +145,7 @@ object HttpUtil {
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to create proxy connection", e)
|
||||
// If an exception occurs, close the connection and return null
|
||||
conn?.disconnect()
|
||||
return null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
@@ -8,6 +9,7 @@ import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.v2ray.ang.AppConfig
|
||||
import java.lang.reflect.Type
|
||||
|
||||
object JsonUtil {
|
||||
@@ -70,7 +72,7 @@ object JsonUtil {
|
||||
try {
|
||||
return JsonParser.parseString(src).getAsJsonObject()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to parse JSON string", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package com.v2ray.ang.util
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.service.V2RayTestService
|
||||
import java.io.Serializable
|
||||
|
||||
object MessageUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to the service.
|
||||
*
|
||||
@@ -46,7 +48,7 @@ object MessageUtil {
|
||||
intent.putExtra("content", content)
|
||||
ctx.startService(intent)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to send message to test service", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +69,7 @@ object MessageUtil {
|
||||
intent.putExtra("content", content)
|
||||
ctx.sendBroadcast(intent)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to send message with action: $action", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.fmt.Hysteria2Fmt
|
||||
@@ -13,7 +13,7 @@ import java.io.File
|
||||
|
||||
object PluginUtil {
|
||||
private const val HYSTERIA2 = "libhysteria2.so"
|
||||
private const val TAG = ANG_PACKAGE
|
||||
|
||||
private val procService: ProcessService by lazy {
|
||||
ProcessService()
|
||||
}
|
||||
@@ -26,13 +26,23 @@ object PluginUtil {
|
||||
* @param domainPort The domain and port information.
|
||||
*/
|
||||
fun runPlugin(context: Context, config: ProfileItem?, domainPort: String?) {
|
||||
Log.d(TAG, "runPlugin")
|
||||
Log.i(AppConfig.TAG, "Starting plugin execution")
|
||||
|
||||
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
|
||||
val configFile = genConfigHy2(context, config, domainPort) ?: return
|
||||
val cmd = genCmdHy2(context, configFile)
|
||||
if (config == null) {
|
||||
Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (config.configType == EConfigType.HYSTERIA2) {
|
||||
Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
|
||||
val configFile = genConfigHy2(context, config, domainPort) ?: return
|
||||
val cmd = genCmdHy2(context, configFile)
|
||||
|
||||
procService.runProcess(context, cmd)
|
||||
procService.runProcess(context, cmd)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Error running plugin", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +61,7 @@ object PluginUtil {
|
||||
* @return The ping delay in milliseconds, or -1 if it fails.
|
||||
*/
|
||||
fun realPingHy2(context: Context, config: ProfileItem?): Long {
|
||||
Log.d(TAG, "realPingHy2")
|
||||
Log.i(AppConfig.TAG, "realPingHy2")
|
||||
val retFailure = -1L
|
||||
|
||||
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
|
||||
@@ -79,18 +89,18 @@ object PluginUtil {
|
||||
* @return The generated configuration file.
|
||||
*/
|
||||
private fun genConfigHy2(context: Context, config: ProfileItem, domainPort: String?): File? {
|
||||
Log.d(TAG, "runPlugin $HYSTERIA2")
|
||||
Log.i(AppConfig.TAG, "runPlugin $HYSTERIA2")
|
||||
|
||||
val socksPort = domainPort?.split(":")?.last()
|
||||
.let { if (it.isNullOrEmpty()) return null else it.toInt() }
|
||||
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return null
|
||||
|
||||
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
|
||||
Log.d(TAG, "runPlugin ${configFile.absolutePath}")
|
||||
Log.i(AppConfig.TAG, "runPlugin ${configFile.absolutePath}")
|
||||
|
||||
configFile.parentFile?.mkdirs()
|
||||
configFile.writeText(JsonUtil.toJson(hy2Config))
|
||||
Log.d(TAG, JsonUtil.toJson(hy2Config))
|
||||
Log.i(AppConfig.TAG, JsonUtil.toJson(hy2Config))
|
||||
|
||||
return configFile
|
||||
}
|
||||
@@ -119,10 +129,10 @@ object PluginUtil {
|
||||
*/
|
||||
private fun stopHy2() {
|
||||
try {
|
||||
Log.d(TAG, "$HYSTERIA2 destroy")
|
||||
Log.i(AppConfig.TAG, "$HYSTERIA2 destroy")
|
||||
procService?.stopProcess()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, e.toString())
|
||||
Log.e(AppConfig.TAG, "Failed to stop Hysteria2 process", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ import java.util.UUID
|
||||
|
||||
object Utils {
|
||||
|
||||
private val IPV4_REGEX =
|
||||
Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||
private val IPV6_REGEX = Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||
|
||||
/**
|
||||
* Convert string to editable for Kotlin.
|
||||
*
|
||||
@@ -46,22 +50,7 @@ object Utils {
|
||||
* @return The index of the value in the array, or -1 if not found.
|
||||
*/
|
||||
fun arrayFind(array: Array<out String>, value: String): Int {
|
||||
for (i in array.indices) {
|
||||
if (array[i] == value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string to an integer.
|
||||
*
|
||||
* @param str The string to parse.
|
||||
* @return The parsed integer, or 0 if parsing fails.
|
||||
*/
|
||||
fun parseInt(str: String): Int {
|
||||
return parseInt(str, 0)
|
||||
return array.indexOf(value)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +60,7 @@ object Utils {
|
||||
* @param default The default value if parsing fails.
|
||||
* @return The parsed integer, or the default value if parsing fails.
|
||||
*/
|
||||
fun parseInt(str: String?, default: Int): Int {
|
||||
fun parseInt(str: String?, default: Int = 0): Int {
|
||||
return str?.toIntOrNull() ?: default
|
||||
}
|
||||
|
||||
@@ -86,7 +75,7 @@ object Utils {
|
||||
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cmb.primaryClip?.getItemAt(0)?.text.toString()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to get clipboard content", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -103,7 +92,7 @@ object Utils {
|
||||
val clipData = ClipData.newPlainText(null, content)
|
||||
cmb.setPrimaryClip(clipData)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to set clipboard content", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,15 +113,17 @@ object Utils {
|
||||
* @return The decoded string, or null if decoding fails.
|
||||
*/
|
||||
fun tryDecodeBase64(text: String?): String? {
|
||||
if (text.isNullOrEmpty()) return null
|
||||
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP).toString(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
Log.i(ANG_PACKAGE, "Parse base64 standard failed $e")
|
||||
Log.e(AppConfig.TAG, "Failed to decode standard base64", e)
|
||||
}
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e")
|
||||
Log.e(AppConfig.TAG, "Failed to decode URL-safe base64", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -147,7 +138,7 @@ object Utils {
|
||||
return try {
|
||||
Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to encode text to base64", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -159,43 +150,38 @@ object Utils {
|
||||
* @return True if the string is a valid IP address, false otherwise.
|
||||
*/
|
||||
fun isIpAddress(value: String?): Boolean {
|
||||
if (value.isNullOrEmpty()) return false
|
||||
|
||||
try {
|
||||
if (value.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
var addr = value
|
||||
if (addr.isEmpty() || addr.isBlank()) {
|
||||
return false
|
||||
}
|
||||
var addr = value.trim()
|
||||
if (addr.isEmpty()) return false
|
||||
|
||||
//CIDR
|
||||
if (addr.indexOf("/") > 0) {
|
||||
if (addr.contains("/")) {
|
||||
val arr = addr.split("/")
|
||||
if (arr.count() == 2 && Integer.parseInt(arr[1]) > -1) {
|
||||
if (arr.size == 2 && arr[1].toIntOrNull() != null && arr[1].toInt() > -1) {
|
||||
addr = arr[0]
|
||||
}
|
||||
}
|
||||
|
||||
// "::ffff:192.168.173.22"
|
||||
// "[::ffff:192.168.173.22]:80"
|
||||
// Handle IPv4-mapped IPv6 addresses
|
||||
if (addr.startsWith("::ffff:") && '.' in addr) {
|
||||
addr = addr.drop(7)
|
||||
} else if (addr.startsWith("[::ffff:") && '.' in addr) {
|
||||
addr = addr.drop(8).replace("]", "")
|
||||
}
|
||||
|
||||
// addr = addr.toLowerCase()
|
||||
val octets = addr.split('.').toTypedArray()
|
||||
val octets = addr.split('.')
|
||||
if (octets.size == 4) {
|
||||
if (octets[3].indexOf(":") > 0) {
|
||||
if (octets[3].contains(":")) {
|
||||
addr = addr.substring(0, addr.indexOf(":"))
|
||||
}
|
||||
return isIpv4Address(addr)
|
||||
}
|
||||
|
||||
// Ipv6addr [2001:abc::123]:8080
|
||||
return isIpv6Address(addr)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to validate IP address", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -217,9 +203,7 @@ object Utils {
|
||||
* @return True if the string is a valid IPv4 address, false otherwise.
|
||||
*/
|
||||
private fun isIpv4Address(value: String): Boolean {
|
||||
val regV4 =
|
||||
Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||
return regV4.matches(value)
|
||||
return IPV4_REGEX.matches(value)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,13 +214,10 @@ object Utils {
|
||||
*/
|
||||
private fun isIpv6Address(value: String): Boolean {
|
||||
var addr = value
|
||||
if (addr.indexOf("[") == 0 && addr.lastIndexOf("]") > 0) {
|
||||
addr = addr.drop(1)
|
||||
addr = addr.dropLast(addr.count() - addr.lastIndexOf("]"))
|
||||
if (addr.startsWith("[") && addr.endsWith("]")) {
|
||||
addr = addr.drop(1).dropLast(1)
|
||||
}
|
||||
val regV6 =
|
||||
Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||
return regV6.matches(addr)
|
||||
return IPV6_REGEX.matches(addr)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,10 +227,10 @@ object Utils {
|
||||
* @return True if the string is a CoreDNS address, false otherwise.
|
||||
*/
|
||||
fun isCoreDNSAddress(s: String): Boolean {
|
||||
return s.startsWith("https")
|
||||
|| s.startsWith("tcp")
|
||||
|| s.startsWith("quic")
|
||||
|| s == "localhost"
|
||||
return s.startsWith("https") ||
|
||||
s.startsWith("tcp") ||
|
||||
s.startsWith("quic") ||
|
||||
s == "localhost"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,21 +240,16 @@ object Utils {
|
||||
* @return True if the string is a valid URL, false otherwise.
|
||||
*/
|
||||
fun isValidUrl(value: String?): Boolean {
|
||||
try {
|
||||
if (value.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
if (Patterns.WEB_URL.matcher(value).matches()
|
||||
|| Patterns.DOMAIN_NAME.matcher(value).matches()
|
||||
|| URLUtil.isValidUrl(value)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (value.isNullOrEmpty()) return false
|
||||
|
||||
return try {
|
||||
Patterns.WEB_URL.matcher(value).matches() ||
|
||||
Patterns.DOMAIN_NAME.matcher(value).matches() ||
|
||||
URLUtil.isValidUrl(value)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
Log.e(AppConfig.TAG, "Failed to validate URL", e)
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,8 +259,12 @@ object Utils {
|
||||
* @param uriString The URI string to open.
|
||||
*/
|
||||
fun openUri(context: Context, uriString: String) {
|
||||
val uri = uriString.toUri()
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||
try {
|
||||
val uri = uriString.toUri()
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to open URI", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +276,7 @@ object Utils {
|
||||
return try {
|
||||
UUID.randomUUID().toString().replace("-", "")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to generate UUID", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -311,7 +291,7 @@ object Utils {
|
||||
return try {
|
||||
URLDecoder.decode(url, Charsets.UTF_8.toString())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to decode URL", e)
|
||||
url
|
||||
}
|
||||
}
|
||||
@@ -326,7 +306,7 @@ object Utils {
|
||||
return try {
|
||||
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to encode URL", e)
|
||||
url
|
||||
}
|
||||
}
|
||||
@@ -339,13 +319,18 @@ object Utils {
|
||||
* @return The content of the asset file as a string.
|
||||
*/
|
||||
fun readTextFromAssets(context: Context?, fileName: String): String {
|
||||
if (context == null) {
|
||||
return ""
|
||||
if (context == null) return ""
|
||||
|
||||
return try {
|
||||
context.assets.open(fileName).use { inputStream ->
|
||||
inputStream.bufferedReader().use { reader ->
|
||||
reader.readText()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to read asset file: $fileName", e)
|
||||
""
|
||||
}
|
||||
val content = context.assets.open(fileName).bufferedReader().use {
|
||||
it.readText()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,11 +340,15 @@ object Utils {
|
||||
* @return The path to the user asset directory.
|
||||
*/
|
||||
fun userAssetPath(context: Context?): String {
|
||||
if (context == null)
|
||||
return ""
|
||||
val extDir = context.getExternalFilesDir(AppConfig.DIR_ASSETS)
|
||||
?: return context.getDir(AppConfig.DIR_ASSETS, 0).absolutePath
|
||||
return extDir.absolutePath
|
||||
if (context == null) return ""
|
||||
|
||||
return try {
|
||||
context.getExternalFilesDir(AppConfig.DIR_ASSETS)?.absolutePath
|
||||
?: context.getDir(AppConfig.DIR_ASSETS, 0).absolutePath
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to get user asset path", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,11 +358,15 @@ object Utils {
|
||||
* @return The path to the backup directory.
|
||||
*/
|
||||
fun backupPath(context: Context?): String {
|
||||
if (context == null)
|
||||
return ""
|
||||
val extDir = context.getExternalFilesDir(AppConfig.DIR_BACKUPS)
|
||||
?: return context.getDir(AppConfig.DIR_BACKUPS, 0).absolutePath
|
||||
return extDir.absolutePath
|
||||
if (context == null) return ""
|
||||
|
||||
return try {
|
||||
context.getExternalFilesDir(AppConfig.DIR_BACKUPS)?.absolutePath
|
||||
?: context.getDir(AppConfig.DIR_BACKUPS, 0).absolutePath
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to get backup path", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,8 +375,13 @@ object Utils {
|
||||
* @return The device ID for XUDP base key.
|
||||
*/
|
||||
fun getDeviceIdForXUDPBaseKey(): String {
|
||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(Charsets.UTF_8)
|
||||
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
||||
return try {
|
||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(Charsets.UTF_8)
|
||||
Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to generate device ID", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,11 +401,10 @@ object Utils {
|
||||
* @return The formatted IPv6 address, or the original address if not valid.
|
||||
*/
|
||||
fun getIpv6Address(address: String?): String {
|
||||
if (address == null) {
|
||||
return ""
|
||||
}
|
||||
if (address.isNullOrEmpty()) return ""
|
||||
|
||||
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
||||
String.format("[%s]", address)
|
||||
"[$address]"
|
||||
} else {
|
||||
address
|
||||
}
|
||||
@@ -431,8 +428,7 @@ object Utils {
|
||||
* @return The URL string with illegal characters replaced.
|
||||
*/
|
||||
fun fixIllegalUrl(str: String): String {
|
||||
return str
|
||||
.replace(" ", "%20")
|
||||
return str.replace(" ", "%20")
|
||||
.replace("|", "%7C")
|
||||
}
|
||||
|
||||
@@ -463,14 +459,15 @@ object Utils {
|
||||
* @return True if the string is a valid subscription URL, false otherwise.
|
||||
*/
|
||||
fun isValidSubUrl(value: String?): Boolean {
|
||||
try {
|
||||
if (value.isNullOrEmpty()) return false
|
||||
if (URLUtil.isHttpsUrl(value)) return true
|
||||
if (URLUtil.isHttpUrl(value) && value.contains(LOOPBACK)) return true
|
||||
if (value.isNullOrEmpty()) return false
|
||||
|
||||
return try {
|
||||
URLUtil.isHttpsUrl(value) ||
|
||||
(URLUtil.isHttpUrl(value) && value.contains(LOOPBACK))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to validate subscription URL", e)
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,7 +486,7 @@ object Utils {
|
||||
*
|
||||
* @return True if the package is Xray, false otherwise.
|
||||
*/
|
||||
fun isXray(): Boolean = (ANG_PACKAGE.startsWith("com.v2ray.ang"))
|
||||
fun isXray(): Boolean = ANG_PACKAGE.startsWith("com.v2ray.ang")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
@@ -61,7 +63,7 @@ object ZipUtil {
|
||||
zos.closeEntry()
|
||||
zos.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to zip folder", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -97,7 +99,7 @@ object ZipUtil {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(AppConfig.TAG, "Failed to unzip file", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServersCache
|
||||
@@ -63,7 +62,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestManager.closeAllTcpSockets()
|
||||
Log.i(ANG_PACKAGE, "Main ViewModel is cleared")
|
||||
Log.i(AppConfig.TAG, "Main ViewModel is cleared")
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
override fun onCleared() {
|
||||
PreferenceManager.getDefaultSharedPreferences(getApplication())
|
||||
.unregisterOnSharedPreferenceChangeListener(this)
|
||||
Log.i(AppConfig.ANG_PACKAGE, "Settings ViewModel is cleared")
|
||||
Log.i(AppConfig.TAG, "Settings ViewModel is cleared")
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
* @param key The key of the changed preference.
|
||||
*/
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
|
||||
Log.i(AppConfig.TAG, "Observe settings changed: $key")
|
||||
when (key) {
|
||||
AppConfig.PREF_MODE,
|
||||
AppConfig.PREF_VPN_DNS,
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
<string name="title_sub_update">بهروزرسانی گروه فعلی اشتراک</string>
|
||||
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
|
||||
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_user_asset_setting">فایل های منبع جغرافیایی</string>
|
||||
<string name="title_sort_by_test_results">مرتب سازی بر اساس نتایج آزمایش</string>
|
||||
<string name="title_filter_config">فیلتر کردن کانفیگها</string>
|
||||
<string name="filter_config_all">همه گروههای اشتراک</string>
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
<string name="title_sub_update">Обновить подписку группы</string>
|
||||
<string name="title_ping_all_server">Проверка профилей группы</string>
|
||||
<string name="title_real_ping_all_server">Время отклика профилей группы</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_user_asset_setting">Файлы ресурсов</string>
|
||||
<string name="title_sort_by_test_results">Сортировка по результатам теста</string>
|
||||
<string name="title_filter_config">Фильтр групп</string>
|
||||
<string name="filter_config_all">Все группы</string>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.9.0"
|
||||
agp = "8.9.1"
|
||||
desugar_jdk_libs = "2.1.5"
|
||||
gradleLicensePlugin = "0.9.8"
|
||||
kotlin = "2.1.20"
|
||||
|
||||
Reference in New Issue
Block a user