Optimize and improve Utils

This commit is contained in:
2dust
2025-03-30 16:28:14 +08:00
parent ee703e6c95
commit 45facff41d
@@ -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.w(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.w(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")
}