Skip to content

Commit 1f02581

Browse files
committed
first commit
0 parents  commit 1f02581

28 files changed

+1388
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea
5+
.DS_Store
6+
/build
7+
/captures

LICENSE

+674
Large diffs are not rendered by default.

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# MiCTS
2+
3+
简体中文  |  [English](/README_en.md)
4+
5+
小米系统桌面开启圈定即搜(Circle to Search)功能
6+
7+
## 操作步骤
8+
9+
1.`桌面设置-系统导航方式`里,选择`全面屏手势`,关闭`隐藏手势提示线`
10+
11+
12+
2. 安装最新版[Google](https://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox)应用,开启`自启动`权限
13+
14+
15+
3. 安装并启用模块,重启手机
16+
17+
18+
4. 长按底部小白条,如果没有反应,尝试在`设置-应用设置-右上角其他设置-默认应用设置-助手和语音输入-数字助理应用`里选择Google

README_en.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# MiCTS
2+
3+
[简体中文](/README.md)  |  English
4+
5+
Enable Circle to Search for Xiaomi System Launcher
6+
7+
## Usage
8+
9+
1. In `Home Screen Settings - System Navigation`, select `Gestures` and turn off `Hide full screen indicator`
10+
11+
12+
2. Install the latest version of [Google](https://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox), turn on `Autostart` in `App info` if it exists
13+
14+
15+
3. Install and enable the module, restart your phone
16+
17+
18+
4. Long press the navigation handle at the bottom to trigger. If it doesn't work, try to select Google in `Settings - Apps - Manage apps - Other Settings in the upper right corner - Default apps - Assist & voice input - Digital Assistant App`

app/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle.kts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
}
5+
6+
android {
7+
namespace = "com.parallelc.micts"
8+
compileSdk = 34
9+
10+
defaultConfig {
11+
applicationId = "com.parallelc.micts"
12+
minSdk = 27
13+
targetSdk = 34
14+
versionCode = 1
15+
versionName = "1.0"
16+
17+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
}
19+
20+
buildTypes {
21+
release {
22+
isMinifyEnabled = false
23+
proguardFiles(
24+
getDefaultProguardFile("proguard-android-optimize.txt"),
25+
"proguard-rules.pro"
26+
)
27+
}
28+
}
29+
compileOptions {
30+
sourceCompatibility = JavaVersion.VERSION_1_8
31+
targetCompatibility = JavaVersion.VERSION_1_8
32+
}
33+
kotlinOptions {
34+
jvmTarget = "1.8"
35+
}
36+
}
37+
38+
dependencies {
39+
compileOnly(project(":libxposed-compat"))
40+
compileOnly(libs.libxposed.api)
41+
implementation(libs.libxposed.service)
42+
}

app/proguard-rules.pro

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile

app/src/main/AndroidManifest.xml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<application
6+
android:label="@string/app_name"
7+
android:description="@string/xposed_description"
8+
android:supportsRtl="true"
9+
/>
10+
11+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package com.parallelc.micts
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.res.Resources
7+
import android.os.Build
8+
import android.os.Bundle
9+
import android.os.IBinder
10+
import android.os.SystemClock
11+
import android.view.MotionEvent
12+
import android.view.View
13+
import android.view.ViewConfiguration
14+
import io.github.libxposed.api.XposedInterface
15+
import io.github.libxposed.api.XposedInterface.AfterHookCallback
16+
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
17+
import io.github.libxposed.api.XposedInterface.Hooker
18+
import io.github.libxposed.api.XposedModule
19+
import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
20+
import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
21+
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
22+
import io.github.libxposed.api.annotations.AfterInvocation
23+
import io.github.libxposed.api.annotations.BeforeInvocation
24+
import io.github.libxposed.api.annotations.XposedHooker
25+
26+
private lateinit var module: ModuleMain
27+
28+
class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule(base, param) {
29+
30+
init {
31+
module = this
32+
}
33+
34+
@XposedHooker
35+
class ContextualSearchIntentHooker : Hooker {
36+
companion object {
37+
@JvmStatic
38+
@AfterInvocation
39+
fun after(callback: AfterHookCallback) {
40+
(callback.result as Intent).putExtra("com.android.contextualsearch.flag_secure_found", false)
41+
}
42+
}
43+
}
44+
45+
class ResourcesHooker {
46+
companion object {
47+
private var R: Class<*>? = null
48+
49+
@SuppressLint("PrivateApi")
50+
fun hook(param: SystemServerLoadedParam) {
51+
R = param.classLoader.loadClass("com.android.internal.R\$string")
52+
module.hook(Resources::class.java.getDeclaredMethod("getString", Int::class.java), GetStringHooker::class.java)
53+
}
54+
55+
@XposedHooker
56+
class GetStringHooker : Hooker {
57+
companion object {
58+
@JvmStatic
59+
@BeforeInvocation
60+
fun before(callback: BeforeHookCallback) {
61+
when (callback.args[0]) {
62+
R!!.getField("config_defaultContextualSearchKey").getInt(null) -> {
63+
callback.returnAndSkip("omni.entry_point")
64+
}
65+
66+
R!!.getField("config_defaultContextualSearchPackageName").getInt(null) -> {
67+
callback.returnAndSkip("com.google.android.googlequicksearchbox")
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
@SuppressLint("PrivateApi")
77+
override fun onSystemServerLoaded(param: SystemServerLoadedParam) {
78+
super.onSystemServerLoaded(param)
79+
80+
runCatching {
81+
ResourcesHooker.hook(param)
82+
val vims = param.classLoader.loadClass("com.android.server.voiceinteraction.VoiceInteractionManagerService\$VoiceInteractionManagerServiceStub")
83+
hook(vims.getDeclaredMethod("getContextualSearchIntent", Bundle::class.java), ContextualSearchIntentHooker::class.java)
84+
}.onFailure { e ->
85+
log("hook system fail", e)
86+
}
87+
}
88+
89+
@XposedHooker
90+
class ReturnTrueHooker : Hooker {
91+
companion object {
92+
@JvmStatic
93+
@BeforeInvocation
94+
fun before(callback: BeforeHookCallback) {
95+
callback.returnAndSkip(true)
96+
}
97+
}
98+
}
99+
100+
@XposedHooker
101+
class ReturnFalseHooker : Hooker {
102+
companion object {
103+
@JvmStatic
104+
@BeforeInvocation
105+
fun before(callback: BeforeHookCallback) {
106+
callback.returnAndSkip(false)
107+
}
108+
}
109+
}
110+
111+
@XposedHooker
112+
class NavStubViewHooker : Hooker {
113+
companion object {
114+
@SuppressLint("PrivateApi")
115+
private val mCheckLongPress = Runnable {
116+
runCatching {
117+
val bundle = Bundle()
118+
bundle.putLong("invocation_time_ms", SystemClock.elapsedRealtime())
119+
bundle.putInt("omni.entry_point", 1)
120+
val iVims = Class.forName("com.android.internal.app.IVoiceInteractionManagerService\$Stub")
121+
val asInterfaceMethod = iVims.getMethod("asInterface", IBinder::class.java)
122+
val getServiceMethod = Class.forName("android.os.ServiceManager").getMethod("getService", String::class.java)
123+
val vimsInstance = asInterfaceMethod.invoke(null, getServiceMethod.invoke(null, "voiceinteraction")) ?: return@Runnable
124+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
125+
val showSessionFromSession = vimsInstance.javaClass.getDeclaredMethod("showSessionFromSession", IBinder::class.java, Bundle::class.java, Integer.TYPE, String::class.java)
126+
showSessionFromSession.invoke(vimsInstance, null, bundle, 7, "hyperOS_home")
127+
} else {
128+
val showSessionFromSession = vimsInstance.javaClass.getDeclaredMethod("showSessionFromSession", IBinder::class.java, Bundle::class.java, Integer.TYPE)
129+
showSessionFromSession.invoke(vimsInstance, null, bundle, 7)
130+
}
131+
}.onFailure { e ->
132+
module.log("NavStubViewHooker mCheckLongPress fail", e)
133+
}
134+
}
135+
136+
@JvmStatic
137+
@AfterInvocation
138+
fun after(callback: AfterHookCallback) {
139+
val view = callback.thisObject as View
140+
view.removeCallbacks(this.mCheckLongPress)
141+
runCatching {
142+
val mCurrAction = callback.thisObject!!.javaClass.getDeclaredField("mCurrAction")
143+
mCurrAction.isAccessible = true
144+
if (mCurrAction.getInt(callback.thisObject) == 0) {
145+
view.postDelayed(this.mCheckLongPress, ViewConfiguration.getLongPressTimeout().toLong())
146+
}
147+
}.onFailure { e ->
148+
module.log("NavStubViewHooker onTouchEvent fail", e)
149+
}
150+
}
151+
}
152+
}
153+
154+
override fun onPackageLoaded(param: PackageLoadedParam) {
155+
super.onPackageLoaded(param)
156+
if (param.packageName != "com.miui.home" || !param.isFirstPackage) return
157+
158+
runCatching {
159+
val circleToSearchHelper = param.classLoader.loadClass("com.miui.home.recents.cts.CircleToSearchHelper")
160+
hook(circleToSearchHelper.getDeclaredMethod("isSceneForbid", Context::class.java, Int::class.java), ReturnFalseHooker::class.java)
161+
hook(circleToSearchHelper.getDeclaredMethod("hasCtsFeature", Context::class.java), ReturnTrueHooker::class.java)
162+
hook(circleToSearchHelper.getDeclaredMethod("isSettingsLongPressHomeAssistantEnabled", Context::class.java), ReturnTrueHooker::class.java)
163+
hook(circleToSearchHelper.getDeclaredMethod("isThirdHome", Context::class.java), ReturnFalseHooker::class.java)
164+
return
165+
}.onFailure { e ->
166+
log("hook CircleToSearchHelper fail", e)
167+
}
168+
169+
runCatching {
170+
val navStubView = param.classLoader.loadClass("com.miui.home.recents.NavStubView")
171+
runCatching { navStubView.getDeclaredField("mCheckLongPress") }
172+
.onSuccess { throw Exception("mCheckLongPress exists") }
173+
.onFailure {
174+
hook(navStubView.getDeclaredMethod("onTouchEvent", MotionEvent::class.java), NavStubViewHooker::class.java)
175+
}
176+
}.onFailure { e ->
177+
log("hook NavStubView fail", e)
178+
}
179+
}
180+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<resources>
2+
<string name="xposed_description">小米系统桌面开启圈定即搜(Circle to Search)功能</string>
3+
</resources>

app/src/main/res/values/strings.xml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<resources>
2+
<string name="app_name" translatable="false">MiCTS</string>
3+
<string name="xposed_description">Enable Circle to Search for Xiaomi System Launcher</string>
4+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.parallelc.micts.ModuleMain
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
minApiVersion=100
2+
targetApiVersion=100
3+
staticScope=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
system
2+
com.miui.home

build.gradle.kts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2+
plugins {
3+
alias(libs.plugins.android.application) apply false
4+
alias(libs.plugins.androidLibrary) apply false
5+
alias(libs.plugins.kotlin.android) apply false
6+
}

gradle.properties

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Project-wide Gradle settings.
2+
# IDE (e.g. Android Studio) users:
3+
# Gradle settings configured through the IDE *will override*
4+
# any settings specified in this file.
5+
# For more details on how to configure your build environment visit
6+
# http://www.gradle.org/docs/current/userguide/build_environment.html
7+
# Specifies the JVM arguments used for the daemon process.
8+
# The setting is particularly useful for tweaking memory settings.
9+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10+
# When configured, Gradle will run in incubating parallel mode.
11+
# This option should only be used with decoupled projects. For more details, visit
12+
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13+
# org.gradle.parallel=true
14+
# AndroidX package structure to make it clearer which packages are bundled with the
15+
# Android operating system, and which are packaged with your app's APK
16+
# https://developer.android.com/topic/libraries/support-library/androidx-rn
17+
android.useAndroidX=true
18+
# Kotlin code style for this project: "official" or "obsolete":
19+
kotlin.code.style=official
20+
# Enables namespacing of each library's R class so that its R class includes only the
21+
# resources declared in the library itself and none from the library's dependencies,
22+
# thereby reducing the size of the R class for that library
23+
android.nonTransitiveRClass=true

gradle/libs.versions.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[versions]
2+
agp = "8.6.1"
3+
kotlin = "1.9.0"
4+
xposed-api = "100"
5+
xposed-service = "100-1.0.0"
6+
7+
[libraries]
8+
libxposed-api = { group = "io.github.libxposed", name = "api", version.ref = "xposed-api" }
9+
libxposed-service = { group = "io.github.libxposed", name = "service", version.ref = "xposed-service" }
10+
11+
[plugins]
12+
android-application = { id = "com.android.application", version.ref = "agp" }
13+
androidLibrary = { id = "com.android.library", version.ref = "agp" }
14+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
15+

gradle/wrapper/gradle-wrapper.jar

57.8 KB
Binary file not shown.
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#Sat Oct 26 03:06:23 CST 2024
2+
distributionBase=GRADLE_USER_HOME
3+
distributionPath=wrapper/dists
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5+
zipStoreBase=GRADLE_USER_HOME
6+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)