Added Activity Proxy function

This commit is contained in:
2022-08-15 05:00:11 +08:00
parent 0f5ec1c912
commit 0ea5f520d1
13 changed files with 1024 additions and 3 deletions

View File

@@ -24,6 +24,10 @@
[filename](public/ModuleApplication.md ':include')
[filename](public/ModuleAppActivity.md ':include')
[filename](public/ModuleAppCompatActivity.md ':include')
[filename](public/YukiModuleResources.md ':include')
[filename](public/YukiResources.md ':include')

View File

@@ -0,0 +1,17 @@
## ModuleAppActivity [class]
```kotlin
open class ModuleAppActivity : Activity()
```
**变更记录**
`v1.0.93` `新增`
**功能描述**
> 代理 `Activity`。
继承于此类的 `Activity` 可以同时在宿主与模块中启动。
在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。

View File

@@ -0,0 +1,33 @@
## ModuleAppCompatActivity [class]
```kotlin
open class ModuleAppCompatActivity : AppCompatActivity()
```
**变更记录**
`v1.0.93` `新增`
**功能描述**
> 代理 `AppCompatActivity`。
继承于此类的 `Activity` 可以同时在宿主与模块中启动。
在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。
在 (Xposed) 宿主环境需要重写 `moduleTheme` 设置 AppCompat 主题,否则会无法启动。
### moduleTheme [field]
```kotlin
open val moduleTheme: Int
```
**变更记录**
`v1.0.93` `新增`
**功能描述**
> 设置当前代理的 `Activity` 主题。

View File

@@ -191,6 +191,150 @@ onAppLifecycle {
}
```
### registerModuleAppActivities [method]
```kotlin
fun Context.registerModuleAppActivities(proxy: Any?)
```
**变更记录**
`v1.0.93` `新增`
**功能描述**
> 向 Hook APP (宿主) 注册当前 Xposed 模块的 `Activity`。
注册成功后,你就可以直接使用 `Context.startActivity` 来启动未在宿主中注册的 `Activity`
你要将需要在宿主启动的 `Activity` 继承于 `ModuleAppActivity``ModuleAppCompatActivity`
为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。
- Kotlin Gradle DSL
```kotlin
androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64")
```
- Groovy
```groovy
aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64'
```
!> 提供的示例资源 ID 值仅供参考,为了防止当前宿主存在多个 Xposed 模块,建议自定义你自己的资源 ID。
!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。
**功能示例**
在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注册当前模块的 `Activity` 代理。
> 示例如下
```kotlin
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
instance<Activity>().registerModuleAppActivities()
}
}
```
你还可以直接在 `AppLifecycle` 中注册当前模块的 `Activity` 代理。
> 示例如下
```kotlin
onAppLifecycle {
onCreate {
registerModuleAppActivities()
}
}
```
如果没有填写 `proxy` 参数API 将会根据当前 `Context` 自动获取当前宿主的启动入口 `Activity` 进行代理。
通常情况下,它是有效的,但是以上情况在一些 APP 中会失效,例如一些 `Activity` 会在注册清单上加入启动参数,那么我们就需要使用另一种解决方案。
若未注册的 `Activity` 不能被正确启动,我们可以手动拿到宿主的 `AndroidManifest.xml` 进行分析,来得到一个注册过的 `Activity` 标签,获取其中的 `name`
你需要选择一个当前宿主可能用不到的、不需要的 `Activity` 作为一个“傀儡”将其进行代理,通常是有效的。
比如我们已经找到了能够被代理的合适 `Activity`
> 示例如下
```xml
<activity
android:name="com.demo.test.activity.TestActivity"
...>
```
根据其中的 `name`,我们只需要在方法中加入这个参数进行注册即可。
> 示例如下
```kotlin
registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity")
```
另一种情况,如果你对宿主的类编写了一个 `stub`,那么你可以直接通过 `Class` 对象来进行注册。
> 示例如下
```kotlin
registerModuleAppActivities(TestActivity::class.java)
```
注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity``ModuleAppCompatActivity`
这些 `Activity` 现在无需注册即可无缝存活于宿主中。
> 示例如下
```kotlin
class HostTestActivity : ModuleAppActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 模块资源已被自动注入,可以直接使用 xml 装载布局
setContentView(R.layout.activity_main)
}
}
```
若你需要继承于 `ModuleAppCompatActivity`,你需要手动设置 AppCompat 主题。
> 示例如下
```kotlin
class HostTestActivity : ModuleAppCompatActivity() {
// 这里的主题名称仅供参考,请填写你模块中已有的主题名称
override val moduleTheme get() = R.style.Theme_AppCompat
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 模块资源已被自动注入,可以直接使用 xml 装载布局
setContentView(R.layout.activity_main)
}
}
```
以上步骤全部完成后,你就可以在 (Xposed) 宿主环境任意存在 `Context` 的地方愉快地调用 `startActivity` 了。
> 示例如下
```kotlin
val context: Context = ... // 假设这就是你的 Context
context.startActivity(context, HostTestActivity::class.java)
```
### ~~isSupportResourcesHook [field]~~ <!-- {docsify-ignore} -->
**变更记录**

View File

@@ -29,6 +29,7 @@
package com.highcapable.yukihookapi.hook.factory
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Resources
@@ -41,6 +42,8 @@ import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
import java.io.BufferedReader
@@ -126,6 +129,20 @@ fun Context.injectModuleAppResources() = resources?.injectModuleAppResources()
*/
fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResources(hostResources = this)
/**
* 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity]
*
* 注册成功后 - 你就可以直接使用 [Context.startActivity] 来启动未在宿主中注册的 [Activity]
*
* - 你要将需要在宿主启动的 [Activity] 继承于 [ModuleAppActivity] 或 [ModuleAppCompatActivity]
*
* 详情请参考 [registerModuleAppActivities](https://fankes.github.io/YukiHookAPI/#/api/document?id=registermoduleappactivities-method)
*
* - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息
* @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity]
*/
fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy)
/**
* 仅判断模块是否在太极、无极中激活
*

View File

@@ -24,11 +24,16 @@
* SOFTWARE.
*
* This file is Created by fankes on 2022/8/14.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
@file:Suppress("QueryPermissionsNeeded")
package com.highcapable.yukihookapi.hook.xposed.parasitic
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.app.Instrumentation
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -36,10 +41,9 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Handler
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.factory.*
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper
@@ -53,6 +57,10 @@ import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources
import com.highcapable.yukihookapi.hook.xposed.bridge.factory.YukiHookHelper
import com.highcapable.yukihookapi.hook.xposed.bridge.factory.YukiMemberHook
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.HandlerDelegate
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.IActivityManagerProxy
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.InstrumentationDelegate
/**
* 这是一个管理 APP 寄生功能的控制类
@@ -69,6 +77,9 @@ internal object AppParasitics {
/** [YukiHookDataChannel] 是否已经注册 */
private var isDataChannelRegister = false
/** [Activity] 代理是否已经注册 */
private var isActivityProxyRegister = false
/** 已被注入到宿主 [Resources] 中的当前 Xposed 模块资源 HashCode 数组 */
private val injectedHostResourcesHashCodes = HashSet<Int>()
@@ -237,6 +248,61 @@ internal object AppParasitics {
dynamicModuleAppResources?.let { moduleAppResources = it }
}
/**
* 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity]
* @param context 当前 [Context]
* @param proxy 代理的 [Activity]
*/
internal fun registerModuleAppActivities(context: Context, proxy: Any?) {
if (isActivityProxyRegister) return
if (YukiHookBridge.hasXposedBridge.not()) return yLoggerW(msg = "You can only register Activity Proxy in Xposed Environment")
runCatching {
ActivityProxyConfig.apply {
proxyIntentName = "${YukiHookBridge.modulePackageName}.ACTIVITY_PROXY_INTENT"
proxyClassName = proxy?.let {
when (it) {
is String, is CharSequence -> it.toString()
is Class<*> -> it.name
else -> error("This proxy [$it] type is not allowed")
}
}?.takeIf { it.isNotBlank() } ?: context.packageManager?.runCatching {
queryIntentActivities(getLaunchIntentForPackage(context.packageName)!!, 0).first().activityInfo.name
}?.getOrNull() ?: ""
if ((proxyClassName.hasClass(context.classLoader) && classOf(proxyClassName, context.classLoader).hasMethod {
name = "setIntent"; param(IntentClass); superClass()
}).not()
) (if (proxyClassName.isBlank()) error("Cound not got launch intent for package \"${context.packageName}\"")
else error("Could not found \"$proxyClassName\" or Class is not a type of Activity"))
}
/** Patched [Instrumentation] */
ActivityThreadClass.field { name = "sCurrentActivityThread" }.ignored().get().any()?.current {
method { name = "getInstrumentation" }
.invoke<Instrumentation>()
?.also { field { name = "mInstrumentation" }.set(InstrumentationDelegate.wrapper(it)) }
HandlerClass.field { name = "mCallback" }.get(field { name = "mH" }.any()).apply {
cast<Handler.Callback?>()?.apply {
if (current().name != classOf<HandlerDelegate>().name) set(HandlerDelegate.wrapper(baseInstance = this))
} ?: set(HandlerDelegate.wrapper())
}
}
/** Patched [ActivityManager] */
runCatching {
runCatching {
ActivityManagerNativeClass.field { name = "gDefault" }.ignored().get().any()
}.getOrNull() ?: ActivityManagerClass.field { name = "IActivityManagerSingleton" }.ignored().get().any()
}.getOrNull()?.also { default ->
SingletonClass.field { name = "mInstance" }.ignored().result {
get(default).apply { any()?.also { set(IActivityManagerProxy.wrapper(IActivityManagerClass, it)) } }
ActivityTaskManagerClass.field { name = "IActivityTaskManagerSingleton" }.ignored().get().any().also { singleton ->
SingletonClass.method { name = "get" }.ignored().get(singleton).call()
get(singleton).apply { any()?.also { set(IActivityManagerProxy.wrapper(IActivityTaskManagerClass, it)) } }
}
}
}
isActivityProxyRegister = true
}.onFailure { yLoggerE(msg = "Activity Proxy initialization failed because got an Exception", e = it) }
}
/**
* 当前 Hook APP (宿主) 的生命周期回调处理类
*/

View File

@@ -0,0 +1,55 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.annotation.CallSuper
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.ModuleClassLoader
/**
* 代理 [Activity]
*
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动
*
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册
*/
open class ModuleAppActivity : Activity() {
override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance()
@CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classOf<ModuleAppActivity>().classLoader
super.onRestoreInstanceState(savedInstanceState)
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.ModuleClassLoader
/**
* 代理 [AppCompatActivity]
*
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动
*
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册
*
* - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 - 否则会无法启动
*/
open class ModuleAppCompatActivity : AppCompatActivity() {
/**
* 设置当前代理的 [Activity] 主题
* @return [Int]
*/
open val moduleTheme get() = -1
override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance()
@CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classOf<ModuleAppActivity>().classLoader
super.onRestoreInstanceState(savedInstanceState)
}
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
if (YukiHookBridge.hasXposedBridge && moduleTheme != -1) setTheme(moduleTheme)
super.onCreate(savedInstanceState)
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 2022/8/14.
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config
import android.app.Activity
import android.content.Intent
/**
* 当前代理的 [Activity] 参数配置类
*/
internal object ActivityProxyConfig {
/**
* 用于代理的 [Intent] 名称
*/
internal var proxyIntentName = ""
/**
* 需要代理的 [Activity] 类名
*/
internal var proxyClassName = ""
}

View File

@@ -0,0 +1,107 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Message
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass
import com.highcapable.yukihookapi.hook.type.android.ClientTransactionClass
import com.highcapable.yukihookapi.hook.type.android.IBinderClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass
import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
/**
* 代理当前 [Handler.Callback]
* @param baseInstance 原始实例
*/
internal class HandlerDelegate private constructor(private val baseInstance: Handler.Callback?) : Handler.Callback {
internal companion object {
/** 启动 [Activity] */
private const val LAUNCH_ACTIVITY = 100
/** 执行事务处理 */
private const val EXECUTE_TRANSACTION = 159
/**
* 从 [Handler.Callback] 创建 [HandlerDelegate] 实例
* @param baseInstance [Handler.Callback] 实例 - 可空
* @return [HandlerDelegate]
*/
internal fun wrapper(baseInstance: Handler.Callback? = null) = HandlerDelegate(baseInstance)
}
override fun handleMessage(msg: Message): Boolean {
when (msg.what) {
LAUNCH_ACTIVITY -> runCatching {
msg.obj.current().field { name = "intent" }.apply {
cast<Intent?>()?.also { intent ->
IntentClass.field { name = "mExtras" }.ignored().get(intent).cast<Bundle?>()
?.classLoader = YukiHookAppHelper.currentApplication()?.classLoader
if (intent.hasExtra(ActivityProxyConfig.proxyIntentName))
set(intent.getParcelableExtra(ActivityProxyConfig.proxyIntentName))
}
}
}.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$LAUNCH_ACTIVITY]", e = it) }
EXECUTE_TRANSACTION -> msg.obj?.runCatching client@{
ClientTransactionClass.method { name = "getCallbacks" }.ignored().get(this).list<Any?>().takeIf { it.isNotEmpty() }
?.forEach { item ->
item?.current()?.takeIf { it.name.contains(other = "LaunchActivityItem") }?.field { name = "mIntent" }?.apply {
cast<Intent?>()?.also { intent ->
IntentClass.field { name = "mExtras" }.ignored().get(intent).cast<Bundle?>()
?.classLoader = YukiHookAppHelper.currentApplication()?.classLoader
if (intent.hasExtra(ActivityProxyConfig.proxyIntentName))
intent.getParcelableExtra<Intent>(ActivityProxyConfig.proxyIntentName).also { subIntent ->
if (Build.VERSION.SDK_INT >= 31)
ActivityThreadClass.method { name = "currentActivityThread" }.ignored().get().call()
?.current()?.method {
name = "getLaunchingActivity"
param(IBinderClass)
}?.call(this@client.current().method { name = "getActivityToken" }.call())
?.current()?.field { name = "intent" }?.set(subIntent)
set(subIntent)
}
}
}
}
}?.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$EXECUTE_TRANSACTION]", e = it) }
}
return baseInstance?.handleMessage(msg) ?: false
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate
import android.app.ActivityManager
import android.content.Intent
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
/**
* 代理当前 [ActivityManager]
* @param baseInstance 原始实例
*/
internal class IActivityManagerProxy private constructor(private val baseInstance: Any) : InvocationHandler {
internal companion object {
/**
* 创建 [IActivityManagerProxy] 代理
* @param clazz 代理的目标 [Class]
* @param instance 代理的目标实例
* @return [Any] 代理包装后的实例
*/
internal fun wrapper(clazz: Class<*>, instance: Any) =
Proxy.newProxyInstance(AppParasitics.baseClassLoader, arrayOf(clazz), IActivityManagerProxy(instance))
}
override fun invoke(proxy: Any?, method: Method?, args: Array<Any>?): Any? {
if (method?.name == "startActivity") args?.indexOfFirst { it is Intent }?.also { index ->
val argsInstance = (args[index] as? Intent) ?: return@also
val component = argsInstance.component
if (component != null &&
component.packageName == YukiHookAppHelper.currentPackageName() &&
component.className.startsWith(YukiHookBridge.modulePackageName)
) args[index] = Intent().apply {
setClassName(component.packageName, ActivityProxyConfig.proxyClassName)
putExtra(ActivityProxyConfig.proxyIntentName, argsInstance)
}
}
return method?.invoke(baseInstance, *(args ?: emptyArray()))
}
}

View File

@@ -0,0 +1,327 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate
import android.app.Activity
import android.app.Application
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.os.*
import android.view.KeyEvent
import android.view.MotionEvent
import com.highcapable.yukihookapi.hook.factory.*
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
/**
* 代理当前 [Instrumentation]
* @param baseInstance 原始实例
*/
internal class InstrumentationDelegate private constructor(private val baseInstance: Instrumentation) : Instrumentation() {
internal companion object {
/**
* 从 [Instrumentation] 创建 [InstrumentationDelegate] 实例
* @param baseInstance [Instrumentation] 实例
* @return [InstrumentationDelegate]
*/
internal fun wrapper(baseInstance: Instrumentation) = InstrumentationDelegate(baseInstance)
}
/**
* 注入当前 [Activity] 生命周期
* @param icicle [Bundle]
*/
private fun Activity.injectLifecycle(icicle: Bundle?) {
if (icicle != null && current().name.startsWith(YukiHookBridge.modulePackageName))
icicle.classLoader = AppParasitics.baseClassLoader
if (current().name.startsWith(YukiHookBridge.modulePackageName)) injectModuleAppResources()
}
override fun newActivity(cl: ClassLoader?, className: String?, intent: Intent?): Activity? = try {
baseInstance.newActivity(cl, className, intent)
} catch (e: Throwable) {
if (className?.startsWith(YukiHookBridge.modulePackageName) == true)
classOf(className).buildOf<Activity>() ?: throw e
else throw e
}
override fun onCreate(arguments: Bundle?) {
baseInstance.onCreate(arguments)
}
override fun start() {
baseInstance.start()
}
override fun onStart() {
baseInstance.onStart()
}
override fun onException(obj: Any?, e: Throwable?) = baseInstance.onException(obj, e)
override fun sendStatus(resultCode: Int, results: Bundle?) {
baseInstance.sendStatus(resultCode, results)
}
override fun addResults(results: Bundle?) {
if (Build.VERSION.SDK_INT >= 26) baseInstance.addResults(results)
}
override fun finish(resultCode: Int, results: Bundle?) {
baseInstance.finish(resultCode, results)
}
override fun setAutomaticPerformanceSnapshots() {
baseInstance.setAutomaticPerformanceSnapshots()
}
override fun startPerformanceSnapshot() {
baseInstance.startPerformanceSnapshot()
}
override fun endPerformanceSnapshot() {
baseInstance.endPerformanceSnapshot()
}
override fun onDestroy() {
baseInstance.onDestroy()
}
override fun getContext(): Context? = baseInstance.context
override fun getComponentName(): ComponentName? = baseInstance.componentName
override fun getTargetContext(): Context? = baseInstance.targetContext
override fun getProcessName(): String? =
if (Build.VERSION.SDK_INT >= 26) baseInstance.processName else AppParasitics.systemContext.processName
override fun isProfiling() = baseInstance.isProfiling
override fun startProfiling() {
baseInstance.startProfiling()
}
override fun stopProfiling() {
baseInstance.stopProfiling()
}
override fun setInTouchMode(inTouch: Boolean) {
baseInstance.setInTouchMode(inTouch)
}
override fun waitForIdle(recipient: Runnable?) {
baseInstance.waitForIdle(recipient)
}
override fun waitForIdleSync() {
baseInstance.waitForIdleSync()
}
override fun runOnMainSync(runner: Runnable?) {
baseInstance.runOnMainSync(runner)
}
override fun startActivitySync(intent: Intent?): Activity? = baseInstance.startActivitySync(intent)
override fun startActivitySync(intent: Intent, options: Bundle?): Activity =
if (Build.VERSION.SDK_INT >= 28) baseInstance.startActivitySync(intent, options) else error("Operating system not supported")
override fun addMonitor(monitor: ActivityMonitor?) {
baseInstance.addMonitor(monitor)
}
override fun addMonitor(cls: String?, result: ActivityResult?, block: Boolean): ActivityMonitor? =
baseInstance.addMonitor(cls, result, block)
override fun addMonitor(filter: IntentFilter?, result: ActivityResult?, block: Boolean): ActivityMonitor? =
baseInstance.addMonitor(filter, result, block)
override fun checkMonitorHit(monitor: ActivityMonitor?, minHits: Int) = baseInstance.checkMonitorHit(monitor, minHits)
override fun waitForMonitor(monitor: ActivityMonitor?): Activity? = baseInstance.waitForMonitor(monitor)
override fun waitForMonitorWithTimeout(monitor: ActivityMonitor?, timeOut: Long): Activity? =
baseInstance.waitForMonitorWithTimeout(monitor, timeOut)
override fun removeMonitor(monitor: ActivityMonitor?) {
baseInstance.removeMonitor(monitor)
}
override fun invokeContextMenuAction(targetActivity: Activity?, id: Int, flag: Int) =
baseInstance.invokeContextMenuAction(targetActivity, id, flag)
override fun invokeMenuActionSync(targetActivity: Activity?, id: Int, flag: Int) =
baseInstance.invokeMenuActionSync(targetActivity, id, flag)
override fun sendCharacterSync(keyCode: Int) {
baseInstance.sendCharacterSync(keyCode)
}
override fun sendKeyDownUpSync(key: Int) {
baseInstance.sendKeyDownUpSync(key)
}
override fun sendKeySync(event: KeyEvent?) {
baseInstance.sendKeySync(event)
}
override fun sendPointerSync(event: MotionEvent?) {
baseInstance.sendPointerSync(event)
}
override fun sendStringSync(text: String?) {
baseInstance.sendStringSync(text)
}
override fun sendTrackballEventSync(event: MotionEvent?) {
baseInstance.sendTrackballEventSync(event)
}
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application? =
baseInstance.newApplication(cl, className, context)
override fun callApplicationOnCreate(app: Application?) {
baseInstance.callApplicationOnCreate(app)
}
override fun newActivity(
clazz: Class<*>?, context: Context?,
token: IBinder?, application: Application?,
intent: Intent?, info: ActivityInfo?,
title: CharSequence?, parent: Activity?,
id: String?, lastNonConfigurationInstance: Any?
): Activity? = baseInstance.newActivity(
clazz, context,
token, application,
intent, info, title,
parent, id, lastNonConfigurationInstance
)
override fun callActivityOnCreate(activity: Activity, icicle: Bundle?, persistentState: PersistableBundle?) {
activity.injectLifecycle(icicle)
baseInstance.callActivityOnCreate(activity, icicle, persistentState)
}
override fun callActivityOnCreate(activity: Activity, icicle: Bundle?) {
activity.injectLifecycle(icicle)
baseInstance.callActivityOnCreate(activity, icicle)
}
override fun callActivityOnDestroy(activity: Activity?) {
baseInstance.callActivityOnDestroy(activity)
}
override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle) {
baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState)
}
override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState, persistentState)
}
override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?) {
baseInstance.callActivityOnPostCreate(activity, savedInstanceState)
}
override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
baseInstance.callActivityOnPostCreate(activity, savedInstanceState, persistentState)
}
override fun callActivityOnNewIntent(activity: Activity?, intent: Intent?) {
baseInstance.callActivityOnNewIntent(activity, intent)
}
override fun callActivityOnStart(activity: Activity?) {
baseInstance.callActivityOnStart(activity)
}
override fun callActivityOnRestart(activity: Activity?) {
baseInstance.callActivityOnRestart(activity)
}
override fun callActivityOnPause(activity: Activity?) {
baseInstance.callActivityOnPause(activity)
}
override fun callActivityOnResume(activity: Activity?) {
baseInstance.callActivityOnResume(activity)
}
override fun callActivityOnStop(activity: Activity?) {
baseInstance.callActivityOnStop(activity)
}
override fun callActivityOnUserLeaving(activity: Activity?) {
baseInstance.callActivityOnUserLeaving(activity)
}
override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle) {
baseInstance.callActivityOnSaveInstanceState(activity, outState)
}
override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle, outPersistentState: PersistableBundle) {
baseInstance.callActivityOnSaveInstanceState(activity, outState, outPersistentState)
}
override fun callActivityOnPictureInPictureRequested(activity: Activity) {
if (Build.VERSION.SDK_INT >= 30) baseInstance.callActivityOnPictureInPictureRequested(activity)
}
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun startAllocCounting() {
baseInstance.startAllocCounting()
}
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun stopAllocCounting() {
baseInstance.stopAllocCounting()
}
override fun getAllocCounts(): Bundle? = baseInstance.allocCounts
override fun getBinderCounts(): Bundle? = baseInstance.binderCounts
override fun getUiAutomation(): UiAutomation? = baseInstance.uiAutomation
override fun getUiAutomation(flags: Int): UiAutomation? =
if (Build.VERSION.SDK_INT >= 24) baseInstance.getUiAutomation(flags) else error("Operating system not supported")
override fun acquireLooperManager(looper: Looper?): TestLooperManager? =
if (Build.VERSION.SDK_INT >= 26) baseInstance.acquireLooperManager(looper) else error("Operating system not supported")
}

View File

@@ -0,0 +1,60 @@
/*
* 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 2022/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
/**
* 自动处理 (Xposed) 宿主环境与模块环境的 [ClassLoader]
*/
internal class ModuleClassLoader private constructor() : ClassLoader(AppParasitics.baseClassLoader) {
internal companion object {
/** 当前 [ModuleClassLoader] 单例 */
private var instance: ModuleClassLoader? = null
/**
* 获取 [ModuleClassLoader] 单例
* @return [ModuleClassLoader]
*/
internal fun instance() = instance ?: ModuleClassLoader().apply { instance = this }
}
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if (YukiHookBridge.hasXposedBridge.not()) return AppParasitics.baseClassLoader.loadClass(name)
return YukiHookAppHelper.currentApplication()?.classLoader?.let { loader ->
runCatching { return@let AppParasitics.baseClassLoader.loadClass(name) }
runCatching { if (name == "androidx.lifecycle.ReportFragment") return@let loader.loadClass(name) }
runCatching { AppParasitics.baseClassLoader.loadClass(name) }.getOrNull() ?: loader.loadClass(name)
} ?: super.loadClass(name, resolve)
}
}