From a3aadafcf5632577e0c2d29f1cb93ea3da6b868c Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Sun, 8 Jan 2023 01:57:03 +0800 Subject: [PATCH] Modify change segmentation of too large data when sending broadcasts data and wrapped the data in YukiHookDataChannel --- .../xposed/channel/YukiHookDataChannel.md | 24 ++ .../xposed/channel/YukiHookDataChannel.md | 24 ++ .../xposed/channel/YukiHookDataChannel.kt | 362 +++++++++++++++--- .../data/wrapper/ChannelDataWrapper.kt | 62 +++ 4 files changed, 429 insertions(+), 43 deletions(-) create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/data/wrapper/ChannelDataWrapper.kt diff --git a/docs-source/src/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md b/docs-source/src/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md index 6b1a5d10..ce3416c2 100644 --- a/docs-source/src/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md +++ b/docs-source/src/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md @@ -66,6 +66,30 @@ inline fun with(initiate: NameSpace.() -> Unit): NameSpace > 创建一个调用空间。 +### allowSendTooLargeData - method + +```kotlin:no-line-numbers +fun allowSendTooLargeData(): NameSpace +``` + +**变更记录** + +`v1.1.5` `added` + +**功能描述** + +> 解除发送数据的大小限制并禁止开启分段发送功能。 + +仅会在每次调用时生效,下一次没有调用此方法则此功能将被自动关闭。 + +你还需要在整个调用域中声明注解 `CauseProblemsApi` 以消除警告。 + +::: danger + +若你不知道允许此功能会带来何种后果,请勿使用。 + +::: + ### put - method ```kotlin:no-line-numbers diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md index 036dd5af..75bae7a9 100644 --- a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.md @@ -58,6 +58,30 @@ inline fun with(initiate: NameSpace.() -> Unit): NameSpace > 创建一个调用空间。 +### allowSendTooLargeData - method + +```kotlin:no-line-numbers +fun allowSendTooLargeData(): NameSpace +``` + +**变更记录** + +`v1.1.5` `新增` + +**功能描述** + +> 解除发送数据的大小限制并禁止开启分段发送功能。 + +仅会在每次调用时生效,下一次没有调用此方法则此功能将被自动关闭。 + +你还需要在整个调用域中声明注解 `CauseProblemsApi` 以消除警告。 + +::: danger + +若你不知道允许此功能会带来何种后果,请勿使用。 + +::: + ### put - method ```kotlin:no-line-numbers diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt index 503e3ecf..b50b3b7c 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt @@ -25,7 +25,7 @@ * * This file is Created by fankes on 2022/5/16. */ -@file:Suppress("StaticFieldLeak", "UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate", "DEPRECATION") +@file:Suppress("StaticFieldLeak", "UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate", "DEPRECATION", "KotlinConstantConditions") package com.highcapable.yukihookapi.hook.xposed.channel @@ -38,9 +38,11 @@ import android.content.Context.ACTIVITY_SERVICE import android.content.Intent import android.content.IntentFilter import android.os.Bundle +import android.os.Parcel import android.os.Parcelable import android.os.TransactionTooLargeException import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.annotation.CauseProblemsApi import com.highcapable.yukihookapi.hook.log.YukiHookLogger import com.highcapable.yukihookapi.hook.log.YukiLoggerData import com.highcapable.yukihookapi.hook.log.yLoggerE @@ -48,10 +50,12 @@ import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData +import com.highcapable.yukihookapi.hook.xposed.channel.data.wrapper.ChannelDataWrapper import com.highcapable.yukihookapi.hook.xposed.channel.priority.ChannelPriority import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper import java.io.Serializable import java.util.concurrent.ConcurrentHashMap +import kotlin.math.round /** * 实现 Xposed 模块的数据通讯桥 @@ -73,6 +77,17 @@ class YukiHookDataChannel private constructor() { /** 是否为 (Xposed) 宿主环境 */ private val isXposedEnvironment = YukiHookBridge.hasXposedBridge + /** + * 系统广播允许发送的最大数据字节大小 + * + * 标准为 1 MB - 实测不同系统目前已知能得到的数据分别有 1、2、3 MB + * + * 经过测试分段发送 900 KB 数据在 1 台 Android 13 系统的设备上依然会发生异常 + * + * 综上所述 - 为了防止系统不同限制不同 - 最终决定设置为 500 KB - 超出后以此大小分段发送数据 + */ + private const val RECEIVER_DATA_MAX_BYTE_SIZE = 500 * 1024 + /** 模块构建版本号获取标签 */ private const val GET_MODULE_GENERATED_VERSION = "module_generated_version_get" @@ -109,6 +124,9 @@ class YukiHookDataChannel private constructor() { /** 当前注册广播的 [Context] */ private var receiverContext: Context? = null + /** 是否允许发送超出 [RECEIVER_DATA_MAX_BYTE_SIZE] 大小的数据 */ + private var isAllowSendTooLargeData = false + /** 广播接收器 */ private val handlerReceiver by lazy { object : BroadcastReceiver() { @@ -138,6 +156,7 @@ class YukiHookDataChannel private constructor() { if (YukiHookAPI.isLoadedFromBaseContext) error("YukiHookDataChannel not allowed in Custom Hook API") if (YukiHookBridge.hasXposedBridge && YukiHookBridge.modulePackageName.isBlank()) error("Xposed modulePackageName load failed, please reset and rebuild it") + isAllowSendTooLargeData = false } /** @@ -206,6 +225,20 @@ class YukiHookDataChannel private constructor() { return NameSpace(context = context ?: receiverContext, packageName, isSecure) } + /** + * 分段数据临时集合实例 + * @param listData [List] 数据数组 + * @param mapData [Map] 数据数组 + * @param setData [Set] 数据数组 + * @param stringData [String] 数据数组 + */ + internal inner class SegmentsTempData( + var listData: ArrayList> = arrayListOf(), + var mapData: ArrayList> = arrayListOf(), + var setData: ArrayList> = arrayListOf(), + var stringData: ArrayList = arrayListOf() + ) + /** * [YukiHookDataChannel] 命名空间 * @@ -216,6 +249,9 @@ class YukiHookDataChannel private constructor() { */ inner class NameSpace internal constructor(private val context: Context?, private val packageName: String, private val isSecure: Boolean) { + /** 当前分段数据临时集合数据 */ + private val segmentsTempData = ConcurrentHashMap() + /** * 键值尾部名称 * @param type 类型 @@ -230,6 +266,22 @@ class YukiHookDataChannel private constructor() { */ private val keyNonRepeatName get() = "_${packageName.hashCode()}" + /** + * 解除发送数据的大小限制并禁止开启分段发送功能 + * + * 仅会在每次调用时生效 - 下一次没有调用此方法则此功能将被自动关闭 + * + * 你还需要在整个调用域中声明注解 [CauseProblemsApi] 以消除警告 + * + * - ❗若你不知道允许此功能会带来何种后果 - 请勿使用 + * @return [NameSpace] + */ + @CauseProblemsApi + fun allowSendTooLargeData(): NameSpace { + isAllowSendTooLargeData = true + return this + } + /** * 创建一个调用空间 * @param initiate 方法体 @@ -242,26 +294,26 @@ class YukiHookDataChannel private constructor() { * @param key 键值名称 * @param value 键值数据 */ - fun put(key: String, value: T) = pushReceiver(ChannelData(key, value)) + fun put(key: String, value: T) = parseSendingData(ChannelData(key, value).toWrapper()) /** * 发送键值数据 * @param data 键值实例 * @param value 键值数据 - 未指定为 [ChannelData.value] */ - fun put(data: ChannelData, value: T? = data.value) = pushReceiver(ChannelData(data.key, value)) + fun put(data: ChannelData, value: T? = data.value) = parseSendingData(ChannelData(data.key, value).toWrapper()) /** * 发送键值数据 * @param data 键值实例 */ - fun put(vararg data: ChannelData<*>) = data.takeIf { it.isNotEmpty() }?.let { pushReceiver(*it) } + fun put(vararg data: ChannelData<*>) = data.takeIf { it.isNotEmpty() }?.forEach { parseSendingData(it.toWrapper()) } /** * 仅发送键值监听 - 使用默认值 [VALUE_WAIT_FOR_LISTENER] 发送键值数据 * @param key 键值名称 */ - fun put(key: String) = pushReceiver(ChannelData(key, VALUE_WAIT_FOR_LISTENER)) + fun put(key: String) = put(key, VALUE_WAIT_FOR_LISTENER) /** * 获取键值数据 @@ -273,7 +325,7 @@ class YukiHookDataChannel private constructor() { receiverCallbacks[key + keyShortName(CallbackKeyType.SINGLE)] = Pair(context) { action, intent -> if (priority == null || priority.result) if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context)) - (intent.extras?.get(key + keyNonRepeatName) as? T?)?.let { result(it) } + parseReceivedData(intent.extras?.get(key + keyNonRepeatName)?.toChannelWrapper(), result) } } @@ -287,7 +339,7 @@ class YukiHookDataChannel private constructor() { receiverCallbacks[data.key + keyShortName(CallbackKeyType.CDATA)] = Pair(context) { action, intent -> if (priority == null || priority.result) if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context)) - (intent.extras?.get(data.key + keyNonRepeatName) as? T?)?.let { result(it) } + parseReceivedData(intent.extras?.get(data.key + keyNonRepeatName)?.toChannelWrapper(), result) } } @@ -303,7 +355,8 @@ class YukiHookDataChannel private constructor() { receiverCallbacks[key + keyShortName(CallbackKeyType.VMFL)] = Pair(context) { action, intent -> if (priority == null || priority.result) if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context)) - if (intent.getStringExtra(key + keyNonRepeatName) == VALUE_WAIT_FOR_LISTENER) callback() + intent.extras?.get(key + keyNonRepeatName)?.toChannelWrapper() + ?.let { if (it.instance.value == VALUE_WAIT_FOR_LISTENER) callback() } } } @@ -336,11 +389,264 @@ class YukiHookDataChannel private constructor() { } /** - * 发送广播 - * @param data 键值数据 + * 接收到的任意类型数据转换为 [ChannelDataWrapper]<[T]> 实例 + * @return [ChannelDataWrapper]<[T]> + * @throws IllegalStateException 如果数据类型不正确 */ - private fun pushReceiver(vararg data: ChannelData<*>) { + private fun Any.toChannelWrapper() = + this as? ChannelDataWrapper ?: error("Received data type ${javaClass.name} is not a vailed YukiHookDataChannel's data") + + /** + * [ChannelData]<[T]> 转换为 [ChannelDataWrapper]<[T]> 实例 + * @param id 包装实例 ID - 默认为 [ChannelDataWrapper.createWrapperId] + * @param size 分段数据总大小 (长度) - 默认为 -1 + * @param index 分段数据当前接收到的下标 - 默认为 -1 + * @return [ChannelDataWrapper]<[T]> + */ + private fun ChannelData.toWrapper(id: String = ChannelDataWrapper.createWrapperId(), size: Int = -1, index: Int = -1) = + ChannelDataWrapper(id, size > 0, size, index, this) + + /** + * 计算 [ChannelData] 所占空间的字节大小 + * @return [Int] 字节大小 + */ + private fun ChannelData<*>.calDataByteSize(): Int { + val bundle = Bundle().apply { + when (value) { + null -> Unit + is Boolean -> putBoolean(key, value as Boolean) + is BooleanArray -> putBooleanArray(key, value as BooleanArray) + is Byte -> putByte(key, value as Byte) + is ByteArray -> putByteArray(key, value as ByteArray) + is Char -> putChar(key, value as Char) + is CharArray -> putCharArray(key, value as CharArray) + is Double -> putDouble(key, value as Double) + is DoubleArray -> putDoubleArray(key, value as DoubleArray) + is Float -> putFloat(key, value as Float) + is FloatArray -> putFloatArray(key, value as FloatArray) + is Int -> putInt(key, value as Int) + is IntArray -> putIntArray(key, value as IntArray) + is Long -> putLong(key, value as Long) + is LongArray -> putLongArray(key, value as LongArray) + is Short -> putShort(key, value as Short) + is ShortArray -> putShortArray(key, value as ShortArray) + is String -> putString(key, value as String) + is Array<*> -> putSerializable(key, value as Array<*>) + is CharSequence -> putCharSequence(key, value as CharSequence) + is Parcelable -> putParcelable(key, value as Parcelable) + is Serializable -> putSerializable(key, value as Serializable) + else -> error("Key-Value type ${value?.javaClass?.name} is not allowed") + } + } + return runCatching { + Parcel.obtain().let { parcel -> + parcel.writeBundle(bundle) + val size = parcel.dataSize() + parcel.recycle() + size + } + }.getOrNull() ?: -1 + } + + /** + * 处理收到的广播数据 + * @param wrapper 键值数据包装类 + * @param result 回调结果数据 + */ + private fun parseReceivedData(wrapper: ChannelDataWrapper?, result: (value: T) -> Unit) { if (YukiHookAPI.Configs.isEnableDataChannel.not()) return + if (wrapper == null) return + if (wrapper.isSegmentsType) runCatching { + val tempData = segmentsTempData[wrapper.wrapperId] ?: SegmentsTempData().apply { segmentsTempData[wrapper.wrapperId] = this } + when (wrapper.instance.value) { + is List<*> -> (wrapper.instance.value as List<*>).also { value -> + if (tempData.listData.isEmpty() && wrapper.segmentsIndex > 0) return + tempData.listData.add(wrapper.segmentsIndex, value) + if (tempData.listData.size == wrapper.segmentsSize) { + result(arrayListOf().also { list -> tempData.listData.forEach { list.addAll(it) } } as T) + tempData.listData.clear() + segmentsTempData.remove(wrapper.wrapperId) + } + } + is Map<*, *> -> (wrapper.instance.value as Map<*, *>).also { value -> + if (tempData.mapData.isEmpty() && wrapper.segmentsIndex > 0) return + tempData.mapData.add(wrapper.segmentsIndex, value) + if (tempData.mapData.size == wrapper.segmentsSize) { + result(hashMapOf().also { map -> tempData.mapData.forEach { it.forEach { (k, v) -> map[k] = v } } } as T) + tempData.mapData.clear() + segmentsTempData.remove(wrapper.wrapperId) + } + } + is Set<*> -> (wrapper.instance.value as Set<*>).also { value -> + if (tempData.setData.isEmpty() && wrapper.segmentsIndex > 0) return + tempData.setData.add(wrapper.segmentsIndex, value) + if (tempData.setData.size == wrapper.segmentsSize) { + result(hashSetOf().also { set -> tempData.setData.forEach { set.addAll(it) } } as T) + tempData.setData.clear() + segmentsTempData.remove(wrapper.wrapperId) + } + } + is String -> (wrapper.instance.value as String).also { value -> + if (tempData.stringData.isEmpty() && wrapper.segmentsIndex > 0) return + tempData.stringData.add(wrapper.segmentsIndex, value) + if (tempData.stringData.size == wrapper.segmentsSize) { + result(StringBuilder().apply { tempData.stringData.forEach { append(it) } }.toString() as T) + tempData.stringData.clear() + segmentsTempData.remove(wrapper.wrapperId) + } + } + else -> yLoggerE(msg = "Unsupported segments data key of \"${wrapper.instance.key}\"'s type") + } + }.onFailure { + yLoggerE(msg = "YukiHookDataChannel cannot merge this segments data key of \"${wrapper.instance.key}\"", e = it) + } else wrapper.instance.value?.let { e -> result(e) } + } + + /** + * 处理需要发送的广播数据 + * @param wrapper 键值数据包装类 + */ + private fun parseSendingData(wrapper: ChannelDataWrapper<*>) { + if (YukiHookAPI.Configs.isEnableDataChannel.not()) return + /** 当前包装实例 ID */ + val wrapperId = ChannelDataWrapper.createWrapperId() + + /** 当前需要发送的数据字节大小 */ + val dataByteSize = wrapper.instance.calDataByteSize() + if (dataByteSize < 0 && isAllowSendTooLargeData.not()) return yLoggerE( + msg = "YukiHookDataChannel cannot calculate the byte size of the data key of \"${wrapper.instance.key}\" to be sent, " + + "so this data cannot be sent\n" + + "If you want to lift this restriction, use the allowSendTooLargeData function when calling, " + + "but this may cause the app crash" + ) + /** + * 如果数据过大打印警告信息 - 仅限 [YukiHookAPI.Configs.isDebug] 启用时生效 + * @param name 数据类型名称 + * @param size 分段总大小 (长度) + */ + fun loggerForTooLargeData(name: String, size: Int) { + if (YukiHookAPI.Configs.isDebug) yLoggerW( + msg = "This data key of \"${wrapper.instance.key}\" type $name is too large (total ${dataByteSize / 1024f} KB, " + + "limit ${RECEIVER_DATA_MAX_BYTE_SIZE / 1024f} KB), will be segmented to $size piece to send" + ) + } + + /** + * 如果数据过大且无法分段打印错误信息 + * @param suggestionMessage 建议内容 - 默认空 + */ + fun loggerForUnprocessableData(suggestionMessage: String = "") = yLoggerE( + msg = "YukiHookDataChannel cannot send this data key of \"${wrapper.instance.key}\" type ${wrapper.instance.value?.javaClass}, " + + "because it is too large (total ${dataByteSize / 1024f} KB, " + + "limit ${RECEIVER_DATA_MAX_BYTE_SIZE / 1024f} KB) and cannot be segmented\n" + + (if (suggestionMessage.isNotBlank()) "$suggestionMessage\n" else "") + + "If you want to lift this restriction, use the allowSendTooLargeData function when calling, " + + "but this may cause the app crash" + ) + + /** + * 计算需要的大小 (预估大小) + * @param size 集合大小 (长度) + * @return [Int] + */ + fun calIntendCountBySize(size: Int) = round(size / round(dataByteSize.toFloat() / RECEIVER_DATA_MAX_BYTE_SIZE)).toInt() + + /** + * 计算需要的大小 (预估大小) + * @return [Int] + */ + fun Collection<*>.calIntendCount() = calIntendCountBySize(size) + + /** + * 计算需要的大小 (预估大小) + * @return [Int] + */ + fun Map<*, *>.calIntendCount() = calIntendCountBySize(size) + when { + wrapper.isSegmentsType || isAllowSendTooLargeData -> pushReceiver(wrapper) + dataByteSize >= RECEIVER_DATA_MAX_BYTE_SIZE -> when (wrapper.instance.value) { + is List<*> -> (wrapper.instance.value as List<*>).also { value -> + val segments = arrayListOf>() + var segment = arrayListOf() + val intendCount = value.calIntendCount() + value.forEach { + segment.add(it) + if (segment.size >= intendCount) { + segments.add(segment) + segment = arrayListOf() + } + } + if (segment.isNotEmpty()) segments.add(segment) + loggerForTooLargeData(name = "List", segments.size) + segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it -> + pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p)) + } + } + is Map<*, *> -> (wrapper.instance.value as Map<*, *>).also { value -> + val segments = arrayListOf>() + var segment = hashMapOf() + val intendCount = value.calIntendCount() + value.forEach { (k, v) -> + segment[k] = v + if (segment.size >= intendCount) { + segments.add(segment) + segment = hashMapOf() + } + } + if (segment.isNotEmpty()) segments.add(segment) + loggerForTooLargeData(name = "Map", segments.size) + segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it -> + pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p)) + } + } + is Set<*> -> (wrapper.instance.value as Set<*>).also { value -> + val segments = arrayListOf>() + var segment = hashSetOf() + val intendCount = value.calIntendCount() + value.forEach { + segment.add(it) + if (segment.size >= intendCount) { + segments.add(segment) + segment = hashSetOf() + } + } + if (segment.isNotEmpty()) segments.add(segment) + loggerForTooLargeData(name = "Set", segments.size) + segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it -> + pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p)) + } + } + is String -> (wrapper.instance.value as String).also { value -> + /** 由于字符会被按照双字节计算 - 所以这里将限制字节大小除以 2 */ + val twoByteMaxSize = RECEIVER_DATA_MAX_BYTE_SIZE / 2 + val segments = arrayListOf() + for (i in 0..value.length step twoByteMaxSize) + if (i + twoByteMaxSize <= value.length) + segments.add(value.substring(i, i + twoByteMaxSize)) + else segments.add(value.substring(i, value.length)) + if (segments.size == 1) return pushReceiver(wrapper) + loggerForTooLargeData(name = "String", segments.size) + segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it -> + pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p)) + } + } + is ByteArray, is CharArray, is ShortArray, + is IntArray, is LongArray, is FloatArray, + is DoubleArray, is BooleanArray, is Array<*> -> loggerForUnprocessableData( + suggestionMessage = "Primitive Array type like String[], int[] ... cannot be segmented, " + + "the suggestion is send those data using List type" + ) + else -> loggerForUnprocessableData() + } + else -> pushReceiver(wrapper) + } + } + + /** + * 发送广播 + * @param wrapper 键值数据包装类 + */ + private fun pushReceiver(wrapper: ChannelDataWrapper<*>) { /** 在 [isSecure] 启用的情况下 - 在模块环境中只能使用 [Activity] 发送广播 */ if (isSecure && context != null) if (isXposedEnvironment.not() && context !is Activity) error("YukiHookDataChannel only support used on an Activity, but this current context is \"${context.javaClass.name}\"") @@ -350,38 +656,8 @@ class YukiHookDataChannel private constructor() { /** 由于系统框架的包名可能不唯一 - 为防止发生问题不再对系统框架的广播设置接收者包名 */ if (packageName != YukiHookBridge.SYSTEM_FRAMEWORK_NAME) setPackage(if (isXposedEnvironment) YukiHookBridge.modulePackageName else packageName) - data.takeIf { it.isNotEmpty() }?.forEach { - when (it.value) { - null -> Unit - is Bundle -> putExtra(it.key + keyNonRepeatName, it.value as Bundle) - is Array<*> -> putExtra(it.key + keyNonRepeatName, it.value as Array<*>) - is Boolean -> putExtra(it.key + keyNonRepeatName, it.value as Boolean) - is BooleanArray -> putExtra(it.key + keyNonRepeatName, it.value as BooleanArray) - is Byte -> putExtra(it.key + keyNonRepeatName, it.value as Byte) - is ByteArray -> putExtra(it.key + keyNonRepeatName, it.value as ByteArray) - is Char -> putExtra(it.key + keyNonRepeatName, it.value as Char) - is CharArray -> putExtra(it.key + keyNonRepeatName, it.value as CharArray) - is Double -> putExtra(it.key + keyNonRepeatName, it.value as Double) - is DoubleArray -> putExtra(it.key + keyNonRepeatName, it.value as DoubleArray) - is Float -> putExtra(it.key + keyNonRepeatName, it.value as Float) - is FloatArray -> putExtra(it.key + keyNonRepeatName, it.value as FloatArray) - is Int -> putExtra(it.key + keyNonRepeatName, it.value as Int) - is IntArray -> putExtra(it.key + keyNonRepeatName, it.value as IntArray) - is Long -> putExtra(it.key + keyNonRepeatName, it.value as Long) - is LongArray -> putExtra(it.key + keyNonRepeatName, it.value as LongArray) - is Short -> putExtra(it.key + keyNonRepeatName, it.value as Short) - is ShortArray -> putExtra(it.key + keyNonRepeatName, it.value as ShortArray) - is String -> putExtra(it.key + keyNonRepeatName, it.value as String) - is CharSequence -> putExtra(it.key + keyNonRepeatName, it.value as CharSequence) - is Parcelable -> putExtra(it.key + keyNonRepeatName, it.value as CharSequence) - is Serializable -> putExtra(it.key + keyNonRepeatName, it.value as Serializable) - else -> error("Key-Value type ${it.value?.javaClass?.name} is not allowed") - } - } - }) ?: yLoggerE( - msg = "Failed to sendBroadcast like \"${data.takeIf { it.isNotEmpty() }?.get(0)?.key ?: "unknown"}\", " + - "because got null context in \"$packageName\"" - ) + putExtra(wrapper.instance.key + keyNonRepeatName, wrapper) + }) ?: yLoggerE(msg = "Failed to sendBroadcast like \"${wrapper.instance.key}\", because got null context in \"$packageName\"") } } } \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/data/wrapper/ChannelDataWrapper.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/data/wrapper/ChannelDataWrapper.kt new file mode 100644 index 00000000..be672b08 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/channel/data/wrapper/ChannelDataWrapper.kt @@ -0,0 +1,62 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2023/1/6. + */ +package com.highcapable.yukihookapi.hook.xposed.channel.data.wrapper + +import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData +import java.io.Serializable +import java.util.* + +/** + * 数据通讯桥键值数据包装类 + * @param wrapperId 包装实例 ID + * @param isSegmentsType 是否为分段数据 + * @param segmentsSize 分段数据总大小 (长度) + * @param segmentsIndex 分段数据当前接收到的下标 + * @param instance 原始数据实例 + */ +internal data class ChannelDataWrapper( + val wrapperId: String, + val isSegmentsType: Boolean, + val segmentsSize: Int, + val segmentsIndex: Int, + val instance: ChannelData +) : Serializable { + + internal companion object { + + /** + * 创建新的包装实例 ID + * @return [String] + */ + internal fun createWrapperId() = Random().let { random -> + var randomId = "" + for (i in 0..5) randomId += ((if (random.nextInt(2) % 2 == 0) 65 else 97) + random.nextInt(26)).toChar() + "$randomId${System.currentTimeMillis()}" + } + } +} \ No newline at end of file