Skip to content

Commit 2e74254

Browse files
committed
feat: support CSService on Android 15
1 parent 26106bb commit 2e74254

File tree

7 files changed

+135
-34
lines changed

7 files changed

+135
-34
lines changed

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MiCTS
22

3-
[![Downloads](https://img.shields.io/github/downloads/parallelcc/MiCTS/total)](https://github.com/parallelcc/MiCTS/releases) [![Release](https://img.shields.io/github/v/release/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS/releases/latest)
3+
[![Stars](https://img.shields.io/github/stars/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS) [![Downloads](https://img.shields.io/github/downloads/parallelcc/MiCTS/total)](https://github.com/parallelcc/MiCTS/releases) [![Release](https://img.shields.io/github/v/release/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS/releases/latest)
44

55
简体中文  |  [English](https://github.com/parallelcc/MiCTS/blob/main/README_en.md)
66

@@ -14,9 +14,10 @@
1414

1515

1616
2. 安装并打开MiCTS
17-
- 如果幸运的话,在不用LSPosed的情况下,打开MiCTS就会直接触发圈定即搜
18-
- 如果没有反应,则需要在LSPosed里激活模块,在[MiCTS设置](#进入设置的方式)里开启`Google机型伪装`后,强制重启Google
19-
- 如果还是没有反应,尝试清除Google的数据,然后打开Google,再强制重启Google
17+
- 如果幸运的话,在不需要root的情况下,打开MiCTS就会直接触发圈定即搜
18+
- 如果没有反应,大概率是因为Google对你的设备禁用了圈定即搜功能(可以通过在Logcat日志中查找`Omni invocation failed: not enabled`确认),在有root的情况下,可以尝试以下方法:
19+
- 在LSPosed里激活模块,在[MiCTS设置](#进入设置的方式)里开启`Google机型伪装`后,强制重启Google
20+
- 如果还是不行,使用[GMS-Flags](https://github.com/polodarb/GMS-Flags),将`com.google.android.apps.search.omnient.device`的flag`45631784`设为true
2021

2122

2223
3. 设置触发方式
@@ -39,8 +40,9 @@
3940
需要在LSPosed里激活模块
4041

4142
- 系统触发服务:触发所使用的系统服务,只会显示当前支持的选项,依赖作用域选择系统框架
42-
- VIS:支持Android 9–15,需要将默认助理应用设置为Google,触发时屏幕边缘会闪,没有激活模块的情况下只能使用此服务
43+
- VIS:支持Android 9–15,需要将默认助理应用设置为Google,触发时一些设备的屏幕边缘会闪,没有激活模块的情况下只能使用此服务
4344
- CSHelper:支持Android 14 QPR3及以上,不需要设置默认助理应用,触发时屏幕边缘不会闪
45+
- CSService:支持Android 15及以上,圈定即搜专用的服务,效果同CSHelper
4446

4547

4648
- 长按小白条触发:仅支持小米设备,依赖作用域选择系统桌面
@@ -57,10 +59,6 @@
5759

5860
## 常见问题
5961

60-
### 需要root吗?
61-
62-
正如[使用方法](#使用方法)第2步中所说,没有root的情况下也可能直接成功触发,这取决于设备的配置,像很多小米设备都可以无需root直接触发,所以你可以尝试一下。但如果不行的话,那原因大概就是没有通过Google的设备检查,因此需要进行机型伪装,这就需要使用LSPosed模块里的功能
63-
6462
### 提示“触发失败!”
6563

6664
大概率是没有将Google设为默认助理,检查一下
@@ -69,7 +67,7 @@
6967

7068
Google不是最新版,更新一下
7169

72-
### 有时屏幕边缘会闪,但无法成功触发,手动打开Google后才会出现刚才圈定即搜的界面
70+
### 有时无法成功触发,手动打开Google后才会出现刚才圈定即搜的界面
7371

7472
原因应该是墓碑机制导致的,看看手机有没有相关的设置可以把Google加到白名单里,比如电池优化选择无限制等,在模块设置里`系统触发服务`使用`CSHelper`应该没有这个问题
7573

README_en.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MiCTS
22

3-
[![Downloads](https://img.shields.io/github/downloads/parallelcc/MiCTS/total)](https://github.com/parallelcc/MiCTS/releases) [![Release](https://img.shields.io/github/v/release/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS/releases/latest)
3+
[![Stars](https://img.shields.io/github/stars/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS) [![Downloads](https://img.shields.io/github/downloads/parallelcc/MiCTS/total)](https://github.com/parallelcc/MiCTS/releases) [![Release](https://img.shields.io/github/v/release/parallelcc/MiCTS)](https://github.com/parallelcc/MiCTS/releases/latest)
44

55
[简体中文](/README.md)  |  English
66

@@ -14,9 +14,10 @@ Trigger Circle to Search on any Android 9–15 device
1414

1515

1616
2. Install and launch MiCTS
17-
- If you're lucky, Circle to Search will be triggered directly without LSPosed when launching MiCTS
18-
- If nothing happened, then activate the module in LSPosed, enable `Device spoof for Google` in the [MiCTS settings](#how-to-enter-settings), and force restart Google
19-
- If it still doesn't work, try clearing Google’s data, then launch Google and force restart it
17+
- If you're lucky, Circle to Search will be triggered directly without root when launching MiCTS
18+
- If nothing happened, most likely it's because Google disabled Circle to Search for your device (you can confirm by checking the message `Omni invocation failed: not enabled` in Logcat). Try the following **with root**:
19+
- Activate the module in LSPosed, enable `Device spoof for Google` in the [MiCTS settings](#how-to-enter-settings), and force restart Google
20+
- If it still doesn't work, then change `com.google.android.apps.search.omnient.device` flag `45631784` to true using [GMS-Flags](https://github.com/polodarb/GMS-Flags)
2021

2122

2223
3. Set up the trigger method
@@ -38,8 +39,9 @@ Trigger Circle to Search on any Android 9–15 device
3839
### Module Settings
3940
Need to activate the module in LSPosed
4041
- System trigger service: The system service used by triggering. Only the services supported will be shown. Need to add System Framework to the scope in LSPosed
41-
- VIS: Supports on Android 9-15. Need to set Google as the default assistant app and the screen edge will flash when triggering. If the module is not activated, only this service will be used
42+
- VIS: Supports on Android 9-15. Need to set Google as the default assistant app and the screen edge will flash when triggering for some devices. If the module is not activated, only this service will be used
4243
- CSHelper: Supports on Android 14 QPR3 and above. Don’t need to set Google as the default assistant app and the screen edge will not flash when triggering
44+
- CSService: Supports on Android 15 and above. A dedicated service for Circle to Search, same effect as CSHelper
4345

4446

4547
- Trigger by long press gesture handle: Only supports on Xiaomi devices. Need to add System Launcher/POCO Launcher to the scope in LSPosed
@@ -56,10 +58,6 @@ Need to activate the module in LSPosed
5658

5759
## FAQ
5860

59-
### Does it require root?
60-
61-
As mentioned in step 2 of [How to Use](#How-to-Use), it's possible to trigger without root, depending on your device's configuration, for example, many Xiaomi devices can trigger directly without root, so you can give it a try. However, if it doesn't work, it's likely because your device didn't pass Google's device check. In this case, you'll need device spoof, which is what the LSPosed module provides
62-
6361
### Prompt "Trigger failed!"
6462

6563
Most likely because Google is not set as the default assistant, check it
@@ -68,7 +66,7 @@ Most likely because Google is not set as the default assistant, check it
6866

6967
Ensure that Google is the latest version
7068

71-
### Sometimes, the screen edge flashed but did not trigger successfully, the interface appeared after opening Google
69+
### Sometimes it doesn't trigger successfully, and the interface appears only after opening Google
7270

7371
This is likely due to the tombstone mechanism. Check if your device has related settings and add Google to the whitelist, such as selecting "No restrictions" in battery saver
7472

app/src/main/java/com/parallelc/micts/ModuleMain.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import com.parallelc.micts.config.TriggerService
66
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
77
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
88
import com.parallelc.micts.config.XposedConfig.KEY_DEVICE_SPOOF
9-
import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
109
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_BRAND
1110
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_DEVICE
1211
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_MANUFACTURER
1312
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_MODEL
13+
import com.parallelc.micts.hooker.CSMSHooker
1414
import com.parallelc.micts.hooker.InvokeOmniHooker
1515
import com.parallelc.micts.hooker.LongPressHomeHooker
1616
import com.parallelc.micts.hooker.NavStubViewHooker
@@ -39,6 +39,15 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
3939
log("hook VIMS fail", e)
4040
}
4141
}
42+
43+
if (TriggerService.getSupportedServices().contains(TriggerService.CSService)) {
44+
runCatching {
45+
CSMSHooker.hook(param)
46+
}.onFailure { e ->
47+
log("hook CSMS fail", e)
48+
}
49+
}
50+
4251
if (Build.MANUFACTURER == "Xiaomi") {
4352
runCatching {
4453
LongPressHomeHooker.hook(param)
@@ -56,7 +65,6 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
5665

5766
when (param.packageName) {
5867
"com.miui.home", "com.mi.android.globallauncher" -> {
59-
if (!prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) return
6068
runCatching {
6169
val circleToSearchHelper = param.classLoader.loadClass("com.miui.home.recents.cts.CircleToSearchHelper")
6270
hook(circleToSearchHelper.getDeclaredMethod("invokeOmni", Context::class.java, Int::class.java, Int::class.java), InvokeOmniHooker::class.java)

app/src/main/java/com/parallelc/micts/config/XposedConfig.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ enum class TriggerService(val isSupported: Boolean) {
88
VIS(true),
99
@SuppressLint("DiscouragedApi")
1010
CSHelper(Resources.getSystem().getIdentifier("config_defaultContextualSearchKey", "string", "android") != 0),
11-
ContextualSearchService(false);
11+
@SuppressLint("PrivateApi")
12+
CSService(
13+
try {
14+
Class.forName("android.app.contextualsearch.ContextualSearchManager")
15+
true
16+
} catch (_: Exception) {
17+
false
18+
}
19+
);
1220

1321
companion object {
1422
fun getSupportedServices(): List<TriggerService> {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.parallelc.micts.hooker
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import android.os.IBinder
6+
import com.parallelc.micts.module
7+
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
8+
import io.github.libxposed.api.XposedInterface.Hooker
9+
import io.github.libxposed.api.XposedInterface.MethodUnhooker
10+
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
11+
import io.github.libxposed.api.annotations.BeforeInvocation
12+
import io.github.libxposed.api.annotations.XposedHooker
13+
import java.lang.reflect.Method
14+
15+
class CSMSHooker {
16+
companion object {
17+
private var enforcePermission: Method? = null
18+
private var getContextualSearchPackageName: Method? = null
19+
private var contextualSearchPackageName: Int = 0
20+
21+
@SuppressLint("PrivateApi")
22+
fun hook(param: SystemServerLoadedParam) {
23+
val rString = param.classLoader.loadClass("com.android.internal.R\$string")
24+
contextualSearchPackageName = rString.getField("config_defaultContextualSearchPackageName").getInt(null)
25+
val systemServer = param.classLoader.loadClass("com.android.server.SystemServer")
26+
module!!.hook(systemServer.getDeclaredMethod("deviceHasConfigString", Context::class.java, Int::class.java), DeviceHasConfigStringHooker::class.java)
27+
28+
val csms = param.classLoader.loadClass("com.android.server.contextualsearch.ContextualSearchManagerService")
29+
enforcePermission = csms.getDeclaredMethod("enforcePermission", String::class.java)
30+
getContextualSearchPackageName = csms.getDeclaredMethod("getContextualSearchPackageName")
31+
}
32+
33+
@SuppressLint("PrivateApi")
34+
fun startContextualSearch(entryPoint: Int): Boolean {
35+
var unhookers = mutableListOf<MethodUnhooker<Method>>()
36+
return runCatching {
37+
unhookers += module!!.hook(enforcePermission!!, EnforcePermissionHooker::class.java)
38+
unhookers += module!!.hook(getContextualSearchPackageName!!, GetCSPackageNameHooker::class.java)
39+
40+
val icsmClass = Class.forName("android.app.contextualsearch.IContextualSearchManager")
41+
val cs = Class.forName("android.os.ServiceManager").getMethod("getService", String::class.java).invoke(null, "contextual_search")
42+
val icsm = Class.forName("android.app.contextualsearch.IContextualSearchManager\$Stub").getMethod("asInterface", IBinder::class.java).invoke(null, cs)
43+
icsmClass.getDeclaredMethod("startContextualSearch", Int::class.java).invoke(icsm, entryPoint)
44+
}.onFailure { e ->
45+
module!!.log("invoke startContextualSearch fail", e)
46+
}.also {
47+
unhookers.forEach { unhooker -> unhooker.unhook() }
48+
}.isSuccess
49+
}
50+
51+
@XposedHooker
52+
class DeviceHasConfigStringHooker : Hooker {
53+
companion object {
54+
@JvmStatic
55+
@BeforeInvocation
56+
fun before(callback: BeforeHookCallback) {
57+
if (callback.args[1] == contextualSearchPackageName) {
58+
callback.returnAndSkip(true)
59+
}
60+
}
61+
}
62+
}
63+
64+
@XposedHooker
65+
class EnforcePermissionHooker : Hooker {
66+
companion object {
67+
@JvmStatic
68+
@BeforeInvocation
69+
fun before(callback: BeforeHookCallback) {
70+
callback.returnAndSkip(null)
71+
}
72+
}
73+
}
74+
75+
@XposedHooker
76+
class GetCSPackageNameHooker : Hooker {
77+
companion object {
78+
@JvmStatic
79+
@BeforeInvocation
80+
fun before(callback: BeforeHookCallback) {
81+
callback.returnAndSkip("com.google.android.googlequicksearchbox")
82+
}
83+
}
84+
}
85+
}
86+
}

app/src/main/java/com/parallelc/micts/hooker/VIMSHooker.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ class VIMSHooker {
2727

2828
@SuppressLint("PrivateApi")
2929
fun hook(param: SystemServerLoadedParam) {
30-
val vims = param.classLoader.loadClass("com.android.server.voiceinteraction.VoiceInteractionManagerService\$VoiceInteractionManagerServiceStub")
30+
val vimsStub = param.classLoader.loadClass("com.android.server.voiceinteraction.VoiceInteractionManagerService\$VoiceInteractionManagerServiceStub")
3131
val rString = param.classLoader.loadClass("com.android.internal.R\$string")
3232
contextualSearchKey = rString.getField("config_defaultContextualSearchKey").getInt(null)
3333
contextualSearchPackageName = rString.getField("config_defaultContextualSearchPackageName").getInt(null)
34-
module!!.hook(vims.getDeclaredMethod("showSessionFromSession", IBinder::class.java, Bundle::class.java, Integer.TYPE, String::class.java), ShowSessionHooker::class.java)
34+
module!!.hook(vimsStub.getDeclaredMethod("showSessionFromSession", IBinder::class.java, Bundle::class.java, Int::class.java, String::class.java), ShowSessionHooker::class.java)
3535
}
3636

3737
@XposedHooker
@@ -40,13 +40,20 @@ class VIMSHooker {
4040
@JvmStatic
4141
@BeforeInvocation
4242
fun before(callback: BeforeHookCallback) : MethodUnhooker<Method>? {
43-
return runCatching {
44-
if (!(callback.args[1] as Bundle).getBoolean("micts_trigger", false)) return@runCatching null
43+
runCatching {
44+
val bundle = callback.args[1] as Bundle
45+
if (!bundle.getBoolean("micts_trigger", false)) return@runCatching null
4546
Binder.clearCallingIdentity()
46-
module!!.hook(Resources::class.java.getDeclaredMethod("getString", Int::class.java), GetStringHooker::class.java)
47+
val triggerService = module!!.getRemotePreferences(CONFIG_NAME).getInt(KEY_TRIGGER_SERVICE, DEFAULT_CONFIG[KEY_TRIGGER_SERVICE] as Int)
48+
if (triggerService == TriggerService.CSService.ordinal) {
49+
callback.returnAndSkip(CSMSHooker.startContextualSearch(bundle.getInt("omni.entry_point")))
50+
} else {
51+
return module!!.hook(Resources::class.java.getDeclaredMethod("getString", Int::class.java), GetStringHooker::class.java)
52+
}
4753
}.onFailure { e ->
4854
module!!.log("hook resources fail", e)
49-
}.getOrNull()
55+
}
56+
return null
5057
}
5158

5259
@JvmStatic

app/src/main/java/com/parallelc/micts/ui/activity/MainActivity.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ fun triggerCircleToSearch(entryPoint: Int): Boolean {
3333
}
3434
}.onFailure { e ->
3535
val errMsg = "triggerCircleToSearch failed: " + e.stackTraceToString()
36-
if (module != null) {
37-
module!!.log(errMsg)
38-
} else {
39-
Log.e("MiCTS", errMsg)
40-
}
36+
module?.log(errMsg) ?: Log.e("MiCTS", errMsg)
4137
}.getOrDefault(false)
4238
}
4339

0 commit comments

Comments
 (0)