diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt index e813384d..956000de 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt @@ -8,6 +8,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext object AppManagerUtil { + /** + * Load the list of network applications. + * + * @param context The context to use. + * @return A list of AppInfo objects representing the network applications. + */ suspend fun loadNetworkAppList(context: Context): ArrayList = withContext(Dispatchers.IO) { val packageManager = context.packageManager diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt index d3d41458..dc0ab7da 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt @@ -10,11 +10,24 @@ import java.util.* object HttpUtil { + /** + * Converts a URL string to its ASCII representation. + * + * @param str The URL string to convert. + * @return The ASCII representation of the URL. + */ fun idnToASCII(str: String): String { val url = URL(str) return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file).toExternalForm() } + /** + * Retrieves the content of a URL as a string. + * + * @param url The URL to fetch content from. + * @param timeout The timeout value in milliseconds. + * @return The content of the URL as a string. + */ fun getUrlContent(url: String, timeout: Int): String { var result: String = "" val conn = createProxyConnection(url, 0, timeout, timeout) ?: return result @@ -27,6 +40,15 @@ object HttpUtil { return result } + /** + * Retrieves the content of a URL as a string with a custom User-Agent header. + * + * @param url The URL to fetch content from. + * @param timeout The timeout value in milliseconds. + * @param httpPort The HTTP port to use. + * @return The content of the URL as a string. + * @throws IOException If an I/O error occurs. + */ @Throws(IOException::class) fun getUrlContentWithUserAgent(url: String?, timeout: Int = 30000, httpPort: Int = 0): String { var currentUrl = url @@ -65,11 +87,10 @@ object HttpUtil { * Creates an HttpURLConnection object connected through a proxy. * * @param urlStr The target URL address. - * @param ip The IP address of the proxy server. * @param port The port of the proxy server. * @param connectTimeout The connection timeout in milliseconds (default is 30000 ms). * @param readTimeout The read timeout in milliseconds (default is 30000 ms). - * @param needStream + * @param needStream Whether the connection needs to support streaming. * @return Returns a configured HttpURLConnection object, or null if it fails. */ fun createProxyConnection( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt index 28e7e956..04e0e167 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt @@ -13,14 +13,33 @@ import java.lang.reflect.Type object JsonUtil { private var gson = Gson() + /** + * Converts an object to its JSON representation. + * + * @param src The object to convert. + * @return The JSON representation of the object. + */ fun toJson(src: Any?): String { return gson.toJson(src) } + /** + * Parses a JSON string into an object of the specified class. + * + * @param src The JSON string to parse. + * @param cls The class of the object to parse into. + * @return The parsed object. + */ fun fromJson(src: String, cls: Class): T { return gson.fromJson(src, cls) } + /** + * Converts an object to its pretty-printed JSON representation. + * + * @param src The object to convert. + * @return The pretty-printed JSON representation of the object, or null if the object is null. + */ fun toJsonPretty(src: Any?): String? { if (src == null) return null @@ -39,6 +58,12 @@ object JsonUtil { return gsonPre.toJson(src) } + /** + * Parses a JSON string into a JsonObject. + * + * @param src The JSON string to parse. + * @return The parsed JsonObject, or null if parsing fails. + */ fun parseString(src: String?): JsonObject? { if (src == null) return null diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/MessageUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/MessageUtil.kt index 0f521f57..079e9138 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/MessageUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/MessageUtil.kt @@ -7,17 +7,37 @@ import com.v2ray.ang.AppConfig import com.v2ray.ang.service.V2RayTestService import java.io.Serializable - object MessageUtil { + /** + * Sends a message to the service. + * + * @param ctx The context. + * @param what The message identifier. + * @param content The message content. + */ fun sendMsg2Service(ctx: Context, what: Int, content: Serializable) { sendMsg(ctx, AppConfig.BROADCAST_ACTION_SERVICE, what, content) } + /** + * Sends a message to the UI. + * + * @param ctx The context. + * @param what The message identifier. + * @param content The message content. + */ fun sendMsg2UI(ctx: Context, what: Int, content: Serializable) { sendMsg(ctx, AppConfig.BROADCAST_ACTION_ACTIVITY, what, content) } + /** + * Sends a message to the test service. + * + * @param ctx The context. + * @param what The message identifier. + * @param content The message content. + */ fun sendMsg2TestService(ctx: Context, what: Int, content: Serializable) { try { val intent = Intent() @@ -30,6 +50,14 @@ object MessageUtil { } } + /** + * Sends a message with the specified action. + * + * @param ctx The context. + * @param action The action string. + * @param what The message identifier. + * @param content The message content. + */ private fun sendMsg(ctx: Context, action: String, what: Int, content: Serializable) { try { val intent = Intent() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/MyContextWrapper.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/MyContextWrapper.kt index 8c0a4c63..a769368f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/MyContextWrapper.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/MyContextWrapper.kt @@ -11,12 +11,18 @@ import java.util.Locale open class MyContextWrapper(base: Context?) : ContextWrapper(base) { companion object { + /** + * Wraps the context with a new locale. + * + * @param context The original context. + * @param newLocale The new locale to set. + * @return A ContextWrapper with the new locale. + */ @RequiresApi(Build.VERSION_CODES.N) fun wrap(context: Context, newLocale: Locale?): ContextWrapper { var mContext = context val res: Resources = mContext.resources val configuration: Configuration = res.configuration - //注意 Android 7.0 前后的不同处理方法 mContext = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { configuration.setLocale(newLocale) val localeList = LocaleList(newLocale) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/PluginUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/PluginUtil.kt index 0f376f6f..a4d62a7b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/PluginUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/PluginUtil.kt @@ -12,17 +12,19 @@ import com.v2ray.ang.service.ProcessService import java.io.File object PluginUtil { - //private const val HYSTERIA2 = "hysteria2-plugin" private const val HYSTERIA2 = "libhysteria2.so" private const val TAG = ANG_PACKAGE private val procService: ProcessService by lazy { ProcessService() } -// fun initPlugin(name: String): PluginManager.InitResult { -// return PluginManager.init(name)!! -// } - + /** + * Run the plugin based on the provided configuration. + * + * @param context The context to use. + * @param config The profile configuration. + * @param domainPort The domain and port information. + */ fun runPlugin(context: Context, config: ProfileItem?, domainPort: String?) { Log.d(TAG, "runPlugin") @@ -34,10 +36,20 @@ object PluginUtil { } } + /** + * Stop the running plugin. + */ fun stopPlugin() { stopHy2() } + /** + * Perform a real ping using Hysteria2. + * + * @param context The context to use. + * @param config The profile configuration. + * @return The ping delay in milliseconds, or -1 if it fails. + */ fun realPingHy2(context: Context, config: ProfileItem?): Long { Log.d(TAG, "realPingHy2") val retFailure = -1L @@ -58,6 +70,14 @@ object PluginUtil { return retFailure } + /** + * Generate the configuration file for Hysteria2. + * + * @param context The context to use. + * @param config The profile configuration. + * @param domainPort The domain and port information. + * @return The generated configuration file. + */ private fun genConfigHy2(context: Context, config: ProfileItem, domainPort: String?): File? { Log.d(TAG, "runPlugin $HYSTERIA2") @@ -75,10 +95,16 @@ object PluginUtil { return configFile } + /** + * Generate the command to run Hysteria2. + * + * @param context The context to use. + * @param configFile The configuration file. + * @return The command to run Hysteria2. + */ private fun genCmdHy2(context: Context, configFile: File): MutableList { return mutableListOf( File(context.applicationInfo.nativeLibraryDir, HYSTERIA2).absolutePath, - //initPlugin(HYSTERIA2).path, "--disable-update-check", "--config", configFile.absolutePath, @@ -88,6 +114,9 @@ object PluginUtil { ) } + /** + * Stop the Hysteria2 process. + */ private fun stopHy2() { try { Log.d(TAG, "$HYSTERIA2 destroy") diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.kt index fb6b2f68..446739b6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.kt @@ -14,13 +14,17 @@ import com.google.zxing.qrcode.QRCodeWriter import java.util.EnumMap /** - * 描述:解析二维码图片 + * QR code decoder utility. */ object QRCodeDecoder { val HINTS: MutableMap = EnumMap(DecodeHintType::class.java) /** - * create qrcode using zxing + * Creates a QR code bitmap from the given text. + * + * @param text The text to encode in the QR code. + * @param size The size of the QR code bitmap. + * @return The generated QR code bitmap, or null if an error occurs. */ fun createQRCode(text: String, size: Int = 800): Bitmap? { return runCatching { @@ -35,22 +39,21 @@ object QRCodeDecoder { }.getOrNull() } - /** - * 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。 + * Decodes a QR code from a local image file. This method is time-consuming and should be called in a background thread. * - * @param picturePath 要解析的二维码图片本地路径 - * @return 返回二维码图片里的内容 或 null + * @param picturePath The local path of the image file to decode. + * @return The content of the QR code, or null if decoding fails. */ fun syncDecodeQRCode(picturePath: String): String? { return syncDecodeQRCode(getDecodeAbleBitmap(picturePath)) } /** - * 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。 + * Decodes a QR code from a bitmap. This method is time-consuming and should be called in a background thread. * - * @param bitmap 要解析的二维码图片 - * @return 返回二维码图片里的内容 或 null + * @param bitmap The bitmap to decode. + * @return The content of the QR code, or null if decoding fails. */ fun syncDecodeQRCode(bitmap: Bitmap?): String? { return bitmap?.let { @@ -70,12 +73,11 @@ object QRCodeDecoder { } } - /** - * 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR + * Converts a local image file to a bitmap that can be decoded as a QR code. The image is compressed to avoid being too large. * - * @param picturePath 本地图片文件路径 - * @return + * @param picturePath The local path of the image file. + * @return The decoded bitmap, or null if an error occurs. */ private fun getDecodeAbleBitmap(picturePath: String): Bitmap? { return try { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt index 54bdd16f..ba3f64ff 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt @@ -30,17 +30,21 @@ import java.util.UUID object Utils { /** - * convert string to editalbe for kotlin + * Convert string to editable for Kotlin. * - * @param text - * @return + * @param text The string to convert. + * @return An Editable instance containing the text. */ fun getEditable(text: String?): Editable { return Editable.Factory.getInstance().newEditable(text.orEmpty()) } /** - * find value in array position + * Find the position of a value in an array. + * + * @param array The array to search. + * @param value The value to find. + * @return The index of the value in the array, or -1 if not found. */ fun arrayFind(array: Array, value: String): Int { for (i in array.indices) { @@ -52,19 +56,31 @@ object Utils { } /** - * parseInt + * 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) } + /** + * Parse a string to an integer with a default value. + * + * @param str The string to parse. + * @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 { return str?.toIntOrNull() ?: default } - /** - * get text from clipboard + * Get text from the clipboard. + * + * @param context The context to use. + * @return The text from the clipboard, or an empty string if an error occurs. */ fun getClipboard(context: Context): String { return try { @@ -77,7 +93,10 @@ object Utils { } /** - * set text to clipboard + * Set text to the clipboard. + * + * @param context The context to use. + * @param content The text to set to the clipboard. */ fun setClipboard(context: Context, content: String) { try { @@ -90,13 +109,21 @@ object Utils { } /** - * base64 decode + * Decode a base64 encoded string. + * + * @param text The base64 encoded string. + * @return The decoded string, or an empty string if decoding fails. */ fun decode(text: String?): String { return tryDecodeBase64(text) ?: text?.trimEnd('=')?.let { tryDecodeBase64(it) }.orEmpty() } - + /** + * Try to decode a base64 encoded string. + * + * @param text The base64 encoded string. + * @return The decoded string, or null if decoding fails. + */ fun tryDecodeBase64(text: String?): String? { try { return Base64.decode(text, Base64.NO_WRAP).toString(Charsets.UTF_8) @@ -112,7 +139,10 @@ object Utils { } /** - * base64 encode + * Encode a string to base64. + * + * @param text The string to encode. + * @return The base64 encoded string, or an empty string if encoding fails. */ fun encode(text: String): String { return try { @@ -123,9 +153,11 @@ object Utils { } } - /** - * is ip address + * Check if a string is a valid IP address. + * + * @param value The string to check. + * @return True if the string is a valid IP address, false otherwise. */ fun isIpAddress(value: String?): Boolean { try { @@ -169,16 +201,34 @@ object Utils { } } + /** + * Check if a string is a pure IP address (IPv4 or IPv6). + * + * @param value The string to check. + * @return True if the string is a pure IP address, false otherwise. + */ fun isPureIpAddress(value: String): Boolean { return isIpv4Address(value) || isIpv6Address(value) } + /** + * Check if a string is a valid IPv4 address. + * + * @param value The string to check. + * @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) } + /** + * Check if a string is a valid IPv6 address. + * + * @param value The string to check. + * @return True if the string is a valid IPv6 address, false otherwise. + */ private fun isIpv6Address(value: String): Boolean { var addr = value if (addr.indexOf("[") == 0 && addr.lastIndexOf("]") > 0) { @@ -190,6 +240,12 @@ object Utils { return regV6.matches(addr) } + /** + * Check if a string is a CoreDNS address. + * + * @param s The string to check. + * @return True if the string is a CoreDNS address, false otherwise. + */ fun isCoreDNSAddress(s: String): Boolean { return s.startsWith("https") || s.startsWith("tcp") @@ -198,7 +254,10 @@ object Utils { } /** - * is valid url + * Check if a string is a valid URL. + * + * @param value The string to check. + * @return True if the string is a valid URL, false otherwise. */ fun isValidUrl(value: String?): Boolean { try { @@ -218,14 +277,21 @@ object Utils { return false } - + /** + * Open a URI in a browser. + * + * @param context The context to use. + * @param uriString The URI string to open. + */ fun openUri(context: Context, uriString: String) { val uri = uriString.toUri() context.startActivity(Intent(Intent.ACTION_VIEW, uri)) } /** - * uuid + * Generate a UUID. + * + * @return A UUID string without dashes. */ fun getUuid(): String { return try { @@ -236,6 +302,12 @@ object Utils { } } + /** + * Decode a URL-encoded string. + * + * @param url The URL-encoded string. + * @return The decoded string, or the original string if decoding fails. + */ fun urlDecode(url: String): String { return try { URLDecoder.decode(url, Charsets.UTF_8.toString()) @@ -245,6 +317,12 @@ object Utils { } } + /** + * Encode a string to URL-encoded format. + * + * @param url The string to encode. + * @return The URL-encoded string, or the original string if encoding fails. + */ fun urlEncode(url: String): String { return try { URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20") @@ -254,9 +332,12 @@ object Utils { } } - /** - * readTextFromAssets + * Read text from an asset file. + * + * @param context The context to use. + * @param fileName The name of the asset file. + * @return The content of the asset file as a string. */ fun readTextFromAssets(context: Context?, fileName: String): String { if (context == null) { @@ -268,6 +349,12 @@ object Utils { return content } + /** + * Get the path to the user asset directory. + * + * @param context The context to use. + * @return The path to the user asset directory. + */ fun userAssetPath(context: Context?): String { if (context == null) return "" @@ -276,6 +363,12 @@ object Utils { return extDir.absolutePath } + /** + * Get the path to the backup directory. + * + * @param context The context to use. + * @return The path to the backup directory. + */ fun backupPath(context: Context?): String { if (context == null) return "" @@ -284,15 +377,32 @@ object Utils { return extDir.absolutePath } + /** + * Get the device ID for XUDP base key. + * + * @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)) } + /** + * Get the dark mode status. + * + * @param context The context to use. + * @return True if dark mode is enabled, false otherwise. + */ fun getDarkModeStatus(context: Context): Boolean { return context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK != UI_MODE_NIGHT_NO } + /** + * Get the IPv6 address in a formatted string. + * + * @param address The IPv6 address. + * @return The formatted IPv6 address, or the original address if not valid. + */ fun getIpv6Address(address: String?): String { if (address == null) { return "" @@ -304,25 +414,55 @@ object Utils { } } + /** + * Get the system locale. + * + * @return The system locale. + */ fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { LocaleList.getDefault()[0] } else { Locale.getDefault() } + /** + * Fix illegal characters in a URL. + * + * @param str The URL string. + * @return The URL string with illegal characters replaced. + */ fun fixIllegalUrl(str: String): String { return str .replace(" ", "%20") .replace("|", "%7C") } + /** + * Remove white space from a string. + * + * @param str The string to process. + * @return The string without white space. + */ fun removeWhiteSpace(str: String?): String? { return str?.replace(" ", "") } + /** + * Check if the device is a TV. + * + * @param context The context to use. + * @return True if the device is a TV, false otherwise. + */ fun isTv(context: Context): Boolean = context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + /** + * Find a free port from a list of ports. + * + * @param ports The list of ports to check. + * @return The first free port found. + * @throws IOException If no free port is found. + */ fun findFreePort(ports: List): Int { for (port in ports) { try { @@ -336,6 +476,12 @@ object Utils { throw IOException("no free port found") } + /** + * Check if a string is a valid subscription URL. + * + * @param value The string to check. + * @return True if the string is a valid subscription URL, false otherwise. + */ fun isValidSubUrl(value: String?): Boolean { try { if (value.isNullOrEmpty()) return false @@ -347,12 +493,22 @@ object Utils { return false } + /** + * Get the receiver flags based on the Android version. + * + * @return The receiver flags. + */ fun receiverFlags(): Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.RECEIVER_EXPORTED } else { ContextCompat.RECEIVER_NOT_EXPORTED } + /** + * Check if the package is Xray. + * + * @return True if the package is Xray, false otherwise. + */ fun isXray(): Boolean = (ANG_PACKAGE.startsWith("com.v2ray.ang")) } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/ZipUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/ZipUtil.kt index 5343f6d8..62677c5d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/ZipUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/ZipUtil.kt @@ -13,6 +13,14 @@ import java.util.zip.ZipOutputStream object ZipUtil { private const val BUFFER_SIZE = 4096 + /** + * Zip the contents of a folder. + * + * @param folderPath The path to the folder to zip. + * @param outputZipFilePath The path to the output zip file. + * @return True if the operation is successful, false otherwise. + * @throws IOException If an I/O error occurs. + */ @Throws(IOException::class) fun zipFromFolder(folderPath: String, outputZipFilePath: String): Boolean { val buffer = ByteArray(BUFFER_SIZE) @@ -59,6 +67,14 @@ object ZipUtil { return true } + /** + * Unzip the contents of a zip file to a folder. + * + * @param zipFile The zip file to unzip. + * @param destDirectory The destination directory. + * @return True if the operation is successful, false otherwise. + * @throws IOException If an I/O error occurs. + */ @Throws(IOException::class) fun unzipToFolder(zipFile: File, destDirectory: String): Boolean { File(destDirectory).run { @@ -72,10 +88,8 @@ object ZipUtil { zip.getInputStream(entry).use { input -> val filePath = destDirectory + File.separator + entry.name if (!entry.isDirectory) { - // if the entry is a file, extracts it extractFile(input, filePath) } else { - // if the entry is a directory, make the directory val dir = File(filePath) dir.mkdir() } @@ -89,6 +103,13 @@ object ZipUtil { return true } + /** + * Extract a file from an input stream. + * + * @param inputStream The input stream to read from. + * @param destFilePath The destination file path. + * @throws IOException If an I/O error occurs. + */ @Throws(IOException::class) private fun extractFile(inputStream: InputStream, destFilePath: String) { val bos = BufferedOutputStream(FileOutputStream(destFilePath))