diff --git a/docs-source/src/en/api/special-features/host-inject.md b/docs-source/src/en/api/special-features/host-inject.md index 7b1fee4c..c42d953d 100644 --- a/docs-source/src/en/api/special-features/host-inject.md +++ b/docs-source/src/en/api/special-features/host-inject.md @@ -1,9 +1,287 @@ -# Host Resource Injection Extension * +# Host Resource Injection Extension + +> This is an extension that injects Module App's Resources, `Activity` components, and `Context` topics into the Host App. + +Before using the following functions, in order to prevent Resource Id from conflicting with each other, you need to modify the Resource Id in the `build.gradle` of the current Xposed Module project. + +- Kotlin Gradle DSL + +```kotlin +android { + androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64") +} +``` + +- Groovy + +```groovy +android { + aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64' +} +``` ::: warning -The current page has not been translated yet. +The sample Resource Id value provided is for reference only, **0x7f** cannot be used, the default is **0x64**. -If necessary, please temporarily switch to the **Simplified Chinese** page, or help us improve the translation of this page. +In order to prevent the existence of multiple Xposed Modules in the current Host App, it is recommended to customize your own Resource Id. + +::: + +## Inject Module App's Resources + +After the Host App is hooked, we can directly inject the `Context` obtained in the Hooker into the current Module App's Resources. + +> The following example + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + instance().also { + // Inject Module App's Resources through Context + it.injectModuleAppResources() + // Get the Host App's Resources directly and inject the Module App's Resources + it.resources.injectModuleAppResources() + // Use the Module App's Resource Id directly + it.getString(R.id.app_name) + } + } +} +``` + +You can also inject current Module App's Resources directly in `AppLifecycle`. + +> The following example + +```kotlin +onAppLifecycle { + onCreate { + // Globally inject Module App's Resources, but only in the global lifecycle + // Methods like ImageView.setImageResource need to be injected separately in Activity + // Inject Module App's Resources through Context + injectModuleAppResources() + // Get the Host App's Resources directly and inject the Module App's Resources + resources.injectModuleAppResources() + // Use the Module App's Resource Id directly + getString(R.id.app_name) + } +} +``` + +::: tip + +For more functions, please refer to the [Context+Resources.injectModuleAppResources](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-resources-injectmoduleappresources-ext-method) method. + +::: + +## Register Module App's Activity + +When the `Activity` of all applications in the Android system starts, it needs to be registered in `AndroidManifest.xml`. + +During the Hook process, if we want to directly start the unregistered `Activity` in the Module App through the Host App, what should we do? + +After the Host App is hooked, we can directly register the `Activity` proxy of the current Module App in the `Context` obtained in the Hooker. + +> The following example + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + instance().registerModuleAppActivities() + } +} +``` + +You can also register the current Module App's `Activity` proxy directly in `AppLifecycle`. + +> The following example + +```kotlin +onAppLifecycle { + onCreate { + registerModuleAppActivities() + } +} +``` + +If the `proxy` parameter is not filled in, the API will automatically obtain the current Host App's launching entry `Activity` for proxying according to the current `Context`. + +Usually, it works, but the above situation will fail in some apps, for example, some `Activity` will add launching parameters to the registration list, so we need to use another solution. + +If the unregistered `Activity` cannot be launched correctly, we can manually get the Host App's `AndroidManifest.xml` for analysis to get a registered `Activity` tag and get the `name`. + +You need to choose an unneeded `Activity` that may not be used by the current Host App as a "puppet" to proxy it, which usually works. + +For example, we have found a suitable `Activity` that can be proxied. + +> The following example + +```xml + +``` + +According to the `name`, we only need to add this parameter to the method for registration. + +> The following example + +```kotlin +registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity") +``` + +Alternatively, if you write a `stub` for the Host App's class, you can register it directly through the `Class` object. + +> The following example + +```kotlin +registerModuleAppActivities(TestActivity::class.java) +``` + +After the registration is complete, extends the `Activity` in the Module App you need to use the Host App to start by `ModuleAppActivity` or `ModuleAppCompatActivity`. + +These `Activity` now live seamlessly in the Host App without registration. + +> The following example + +```kotlin +class HostTestActivity : ModuleAppActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Module App's Resources have been injected automatically + // You can directly use xml to load the layout + setContentView(R.layout.activity_main) + } +} +``` + +If you need to extends `ModuleAppCompatActivity`, you need to set the AppCompat theme manually. + +> The following example + +```kotlin +class HostTestActivity : ModuleAppCompatActivity() { + + // The theme name here is for reference only + // Please fill in the theme name already in your Module App + override val moduleTheme get() = R.style.Theme_AppCompat + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Module App's Resources have been injected automatically + // You can directly use xml to load the layout + setContentView(R.layout.activity_main) + } +} +``` + +After all the above steps are completed, you can happily call `startActivity` anywhere in the (Xposed) Host environment where a `Context` exists. + +> The following example + +```kotlin +val context: Context = ... // Assume this is your Context +context.startActivity(context, HostTestActivity::class.java) +``` + +::: tip + +For more functions, please refer to the [Context.registerModuleAppActivities](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-registermoduleappactivities-ext-method) method. + +::: + +## Create ContextThemeWrapper Proxy + +Sometimes, we need to use `MaterialAlertDialogBuilder` to beautify our own dialogs in the Host App, but we can't create them without the AppCompat theme. + +- Will got the following exception + +```:no-line-numbers +The style on this component requires your app theme to be Theme.AppCompat (or a descendant). +``` + +At this time, we want to use `MaterialAlertDialogBuilder` to create a dialog in the current `Activity` of the Host App being hooked, you can have the following methods. + +> The following example + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + // Use applyModuleTheme to create a theme resource in the current Module App + val appCompatContext = instance().applyModuleTheme(R.style.Theme_AppCompat) + // Directly use this Context that wraps the Module App's theme to create a dialog + MaterialAlertDialogBuilder(appCompatContext) + .setTitle("AppCompat Theme Dialog") + .setMessage("I am an AppCompat theme dialog displayed in the Host App.") + .setPositiveButton("OK", null) + .show() + } +} +``` + +You can also set the system (native) night mode and day mode on the current `Context` through `uiMode`. + +Which requires at least Android 10 and above system version support and the current theme contains night mode related elements. + +> The following example + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + // Define the theme resource in the current Module App + var appCompatContext: ModuleContextThemeWrapper + // Get the Configuration object directly to set + appCompatContext = instance() + .applyModuleTheme(R.style.Theme_AppCompat) + .applyConfiguration { uiMode = Configuration.UI_MODE_NIGHT_YES } + // Create a new Configuration object + // This solution will destroy the original font scaling and other settings in the current Host App + // You need to manually re-pass parameters such as densityDpi + appCompatContext = instance().applyModuleTheme( + theme = R.style.Theme_AppCompat, + configuration = Configuration().apply { uiMode = Configuration.UI_MODE_NIGHT_YES } + ) + // Directly use this Context that wraps the Module App's theme to create a dialog + MaterialAlertDialogBuilder(appCompatContext) + .setTitle("AppCompat Theme Dialog") + .setMessage("I am an AppCompat theme dialog displayed in the Host App.") + .setPositiveButton("OK", null) + .show() + } +} +``` + +This way, we can create dialogs in the Host App very simply using `MaterialAlertDialogBuilder`. + +::: warning Possible Problems + +Because some **androidx** dependent libraries or custom themes used by some apps may interfere with the actual style of the current **MaterialAlertDialog**, such as the button style of the dialog. + +You can refer to the **Module App Demo** in this case and see [here is the sample code](https://github.com/fankes/YukiHookAPI/tree/master/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/factory/ComponentCompatFactory.kt) to fix this problem. + +**ClassCastException** may occur when some apps are created, please manually specify a new **Configuration** instance to fix. + +::: + +::: tip + +For more functions, please refer to the [Context.applyModuleTheme](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-applymoduletheme-ext-method) method. ::: \ No newline at end of file