Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 3 additions & 29 deletions .github/workflows/ci_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,23 @@ jobs:
echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks
fi

- name: Checkout libxposed/api
uses: actions/checkout@v4
with:
repository: libxposed/api
path: libxposed/api

- name: Checkout libxposed/service
uses: actions/checkout@v4
with:
repository: libxposed/service
path: libxposed/service

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "21"
java-version: "17"

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true

- name: Build dependencies
working-directory: libxposed
- name: Build MiCTS Release
run: |
echo 'org.gradle.caching=true' >> ~/.gradle/gradle.properties
echo 'org.gradle.parallel=true' >> ~/.gradle/gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC' >> ~/.gradle/gradle.properties
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties
cd api
./gradlew publishToMavenLocal
cd ../service
./gradlew publishToMavenLocal

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"

- name: Build MiCTS Release
run: |
./gradlew :app:assembleMiCTSRelease
echo "MiCTS_APK_PATH=$(find app/build/outputs/apk/MiCTS/release -name '*.apk')" >> $GITHUB_ENV

Expand Down
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ dependencies {
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.accompanist.drawablepainter)
compileOnly(project(":libxposed-compat"))
compileOnly(libs.libxposed.api)
implementation(libs.libxposed.service)
implementation(libs.hiddenapibypass)
Expand Down
38 changes: 20 additions & 18 deletions app/src/main/java/com/parallelc/micts/ModuleMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.parallelc.micts

import android.content.Context
import android.os.Build
import android.util.Log
import com.parallelc.micts.config.TriggerService
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
Expand All @@ -18,37 +19,36 @@ import com.parallelc.micts.hooker.NavBarEventHelperHooker
import com.parallelc.micts.hooker.NavStubGestureEventManagerHooker
import com.parallelc.micts.hooker.NavStubViewHooker
import com.parallelc.micts.hooker.VIMSHooker
import io.github.libxposed.api.XposedInterface
import io.github.libxposed.api.XposedModule
import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
import io.github.libxposed.api.XposedModuleInterface.PackageReadyParam
import io.github.libxposed.api.XposedModuleInterface.SystemServerStartingParam

var module: ModuleMain? = null

class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule(base, param) {
class ModuleMain : XposedModule() {

init {
override fun onModuleLoaded(param: ModuleLoadedParam) {
module = this
}

override fun onSystemServerLoaded(param: SystemServerLoadedParam) {
super.onSystemServerLoaded(param)
override fun onSystemServerStarting(param: SystemServerStartingParam) {
super.onSystemServerStarting(param)

if (BuildConfig.APP_NAME == "MiCTS") {
if (TriggerService.getSupportedServices().contains(TriggerService.CSHelper)) {
runCatching {
VIMSHooker.hook(param)
}.onFailure { e ->
log("hook VIMS fail", e)
log(Log.ERROR, "MiCTS", "hook VIMS fail", e)
}
}

if (TriggerService.getSupportedServices().contains(TriggerService.CSService)) {
runCatching {
CSMSHooker.hook(param)
}.onFailure { e ->
log("hook CSMS fail", e)
log(Log.ERROR, "MiCTS", "hook CSMS fail", e)
}
}
}
Expand All @@ -57,13 +57,13 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
runCatching {
LongPressHomeHooker.hook(param)
}.onFailure { e ->
log("hook LongPressHome fail", e)
log(Log.ERROR, "MiCTS", "hook LongPressHome fail", e)
}
}
}

override fun onPackageLoaded(param: PackageLoadedParam) {
super.onPackageLoaded(param)
override fun onPackageReady(param: PackageReadyParam) {
super.onPackageReady(param)
if (!param.isFirstPackage) return

val prefs = getRemotePreferences(CONFIG_NAME)
Expand All @@ -73,22 +73,24 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
val skipHookTouch = runCatching {
NavStubGestureEventManagerHooker.hook(param)
}.onFailure { e ->
log("hook NavStubGestureEventManager fail", e)
log(Log.ERROR, "MiCTS", "hook NavStubGestureEventManager fail", e)
}.recoverCatching {
val circleToSearchHelper = param.classLoader.loadClass("com.miui.home.recents.cts.CircleToSearchHelper")
hook(circleToSearchHelper.getDeclaredMethod("invokeOmni", Context::class.java, Int::class.java, Int::class.java), InvokeOmniHooker::class.java)
hook(
circleToSearchHelper.getDeclaredMethod("invokeOmni", Context::class.java, Int::class.java, Int::class.java)
).intercept(InvokeOmniHooker())
}.onFailure { e ->
log("hook CircleToSearchHelper fail", e)
log(Log.ERROR, "MiCTS", "hook CircleToSearchHelper fail", e)
}.recoverCatching {
NavBarEventHelperHooker.hook(param)
}.onFailure { e ->
log("hook NavBarEventHelper fail", e)
log(Log.ERROR, "MiCTS", "hook NavBarEventHelper fail", e)
}.isSuccess

runCatching {
NavStubViewHooker.hook(param, skipHookTouch)
}.onFailure { e ->
log("hook NavStubView fail", e)
log(Log.ERROR, "MiCTS", "hook NavStubView fail", e)
}
}
"com.google.android.googlequicksearchbox" -> {
Expand All @@ -112,7 +114,7 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
runCatching {
NavBarActionsConfigHooker.hook(param)
}.onFailure { e ->
log("hook NavBarActionsConfig fail", e)
log(Log.ERROR, "MiCTS", "hook NavBarActionsConfig fail", e)
}
}
}
Expand Down
55 changes: 21 additions & 34 deletions app/src/main/java/com/parallelc/micts/hooker/CSMSHooker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package com.parallelc.micts.hooker
import android.annotation.SuppressLint
import android.content.Context
import android.os.IBinder
import android.util.Log
import com.parallelc.micts.module
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
import io.github.libxposed.api.XposedInterface.Chain
import io.github.libxposed.api.XposedInterface.Hooker
import io.github.libxposed.api.XposedInterface.MethodUnhooker
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
import io.github.libxposed.api.annotations.BeforeInvocation
import io.github.libxposed.api.annotations.XposedHooker
import io.github.libxposed.api.XposedInterface.HookHandle
import io.github.libxposed.api.XposedModuleInterface.SystemServerStartingParam
import java.lang.reflect.Method

class CSMSHooker {
Expand All @@ -19,11 +18,12 @@ class CSMSHooker {
private var contextualSearchPackageName: Int = 0

@SuppressLint("PrivateApi")
fun hook(param: SystemServerLoadedParam) {
fun hook(param: SystemServerStartingParam) {
val rString = param.classLoader.loadClass("com.android.internal.R\$string")
contextualSearchPackageName = rString.getField("config_defaultContextualSearchPackageName").getInt(null)
val systemServer = param.classLoader.loadClass("com.android.server.SystemServer")
module!!.hook(systemServer.getDeclaredMethod("deviceHasConfigString", Context::class.java, Int::class.java), DeviceHasConfigStringHooker::class.java)
module!!.hook(systemServer.getDeclaredMethod("deviceHasConfigString", Context::class.java, Int::class.java))
.intercept(DeviceHasConfigStringHooker())

val csms = param.classLoader.loadClass("com.android.server.contextualsearch.ContextualSearchManagerService")
enforcePermission = csms.getDeclaredMethod("enforcePermission", String::class.java)
Expand All @@ -32,54 +32,41 @@ class CSMSHooker {

@SuppressLint("PrivateApi")
fun startContextualSearch(entryPoint: Int): Boolean {
var unhookers = mutableListOf<MethodUnhooker<Method>>()
var hooks = mutableListOf<HookHandle>()
return runCatching {
unhookers += module!!.hook(enforcePermission!!, EnforcePermissionHooker::class.java)
unhookers += module!!.hook(getContextualSearchPackageName!!, GetCSPackageNameHooker::class.java)
hooks += module!!.hook(enforcePermission!!).intercept(EnforcePermissionHooker())
hooks += module!!.hook(getContextualSearchPackageName!!).intercept(GetCSPackageNameHooker())

Comment thread
parallelcc marked this conversation as resolved.
val icsmClass = Class.forName("android.app.contextualsearch.IContextualSearchManager")
val cs = Class.forName("android.os.ServiceManager").getMethod("getService", String::class.java).invoke(null, "contextual_search")
val icsm = Class.forName("android.app.contextualsearch.IContextualSearchManager\$Stub").getMethod("asInterface", IBinder::class.java).invoke(null, cs)
icsmClass.getDeclaredMethod("startContextualSearch", Int::class.java).invoke(icsm, entryPoint)
}.onFailure { e ->
module!!.log("invoke startContextualSearch fail", e)
module!!.log(Log.ERROR, "MiCTS", "invoke startContextualSearch fail", e)
}.also {
unhookers.forEach { unhooker -> unhooker.unhook() }
hooks.forEach { hook -> hook.unhook() }
}.isSuccess
}

@XposedHooker
class DeviceHasConfigStringHooker : Hooker {
companion object {
@JvmStatic
@BeforeInvocation
fun before(callback: BeforeHookCallback) {
if (callback.args[1] == contextualSearchPackageName) {
callback.returnAndSkip(true)
}
override fun intercept(chain: Chain): Any? {
return if (chain.args[1] == contextualSearchPackageName) {
true
} else {
chain.proceed()
}
}
}

@XposedHooker
class EnforcePermissionHooker : Hooker {
companion object {
@JvmStatic
@BeforeInvocation
fun before(callback: BeforeHookCallback) {
callback.returnAndSkip(null)
}
override fun intercept(chain: Chain): Any? {
return null
}
}

@XposedHooker
class GetCSPackageNameHooker : Hooker {
companion object {
@JvmStatic
@BeforeInvocation
fun before(callback: BeforeHookCallback) {
callback.returnAndSkip("com.google.android.googlequicksearchbox")
}
override fun intercept(chain: Chain): Any? {
return "com.google.android.googlequicksearchbox"
}
}
}
Expand Down
29 changes: 11 additions & 18 deletions app/src/main/java/com/parallelc/micts/hooker/InvokeOmniHooker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,20 @@ import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
import com.parallelc.micts.config.XposedConfig.KEY_VIBRATE
import com.parallelc.micts.module
import com.parallelc.micts.ui.activity.triggerCircleToSearch
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
import io.github.libxposed.api.XposedInterface.Hooker
import io.github.libxposed.api.annotations.BeforeInvocation
import io.github.libxposed.api.annotations.XposedHooker
import io.github.libxposed.api.XposedInterface.Chain

@XposedHooker
class InvokeOmniHooker : Hooker {
companion object {
@JvmStatic
@BeforeInvocation
fun before(callback: BeforeHookCallback) {
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
callback.returnAndSkip(
triggerCircleToSearch(
callback.args[2] as Int,
callback.args[0] as Context?,
prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean)
)
)
}
override fun intercept(chain: Chain): Any? {
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
return if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
triggerCircleToSearch(
chain.args[2] as Int,
chain.args[0] as Context?,
prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean)
)
} else {
chain.proceed()
}
}
}
Loading
Loading